use std::{collections::HashMap, ops::Range}; use bstr::{BStr, BString, ByteSlice, ByteVec};use gix_sec::Trust;use smallvec::SmallVec; use crate::{ AsBStrOpt, file::{ self, Index, IntoBStringOpt, SectionData, SectionId, SectionLookup, SectionRef, Size, mutable::{Whitespace, escape_value}, }, lookup, parse::{self, Event, Span, section::ValueName}, value::normalize,}; /// A opaque type that represents a mutable reference to a section.#[derive(Debug)]pub struct SectionMut<'a> { section: &'a mut SectionData, backing: &'a mut Vec<u8>, /// Present only if backed by [`crate::File`] lookup: Option<LookupMut<'a>>, implicit_newline: bool, whitespace: Whitespace, newline: SmallVec<[u8; 2]>,} #[derive(Debug)]pub(crate) struct LookupMut<'a> { pub(crate) tree: &'a mut HashMap<parse::section::Name, SectionLookup>, pub(crate) order: &'a [SectionId],} /// Mutating methods.impl SectionMut<'_> { /// Rename this section to `name` and the optional `subsection_name`. /// /// Section names are not unique, and renaming into an existing section name is permitted. pub fn rename( &mut self, name: impl AsRef<str>, subsection_name: impl IntoBStringOpt, ) -> Result<&mut Self, parse::section::header::Error> { let header = parse::section::HeaderData::new_in(name, subsection_name.into_bstring_opt(), self.backing)?; self.set_header(header); Ok(self) } /// Adds an entry to the end of this section name `value_name` and `value`. If `value` is `None`, no equal sign will be written leaving /// just the key. This is useful for boolean values which are true if merely the key exists. pub fn push( &mut self, value_name: impl AsRef<str>, value: impl AsBStrOpt, ) -> Result<&mut Self, file::section::value::Error> { let value_name = ValueName::try_from(value_name.as_ref())?; self.push_with_comment_inner(value_name, value.as_bstr_opt(), None)?; Ok(self) } /// Adds an entry to the end of this section name `value_name` and `value`. If `value` is `None`, no equal sign will be written leaving /// just the key. This is useful for boolean values which are true if merely the key exists. /// `comment` has to be the text to put right after the value and behind a `#` character. Note that newlines are silently transformed /// into spaces. pub fn push_with_comment( &mut self, value_name: impl AsRef<str>, value: impl AsBStrOpt, comment: impl crate::AsBStr, ) -> Result<&mut Self, file::section::value::Error> { let value_name = ValueName::try_from(value_name.as_ref())?; self.push_with_comment_inner(value_name, value.as_bstr_opt(), Some(comment.as_bstr()))?; Ok(self) } fn push_with_comment_inner( &mut self, value_name: ValueName, value: Option<&BStr>, comment: Option<&BStr>, ) -> Result<(), parse::span::Error> { let mut events = Vec::new(); if let Some(ws) = &self.whitespace.pre_key { events.push(Event::Whitespace(Span::append(self.backing, ws)?)); } events.push(Event::SectionValueName(Span::append( self.backing, value_name.0.as_slice(), )?)); match value { Some(value) => { events.extend(self.whitespace.key_value_separators(self.backing)?); events.push(Event::Value(Span::append(self.backing, &escape_value(value))?)); } None => events.push(Event::Value(Span::append(self.backing, b"")?)), } if let Some(comment) = comment { events.push(Event::Whitespace(Span::append(self.backing, b" ")?)); let mut c = Vec::with_capacity(comment.len()); let mut bytes = comment.iter().peekable(); if !bytes.peek().is_none_or(|b| b.is_ascii_whitespace()) { c.insert(0, b' '); } c.extend(bytes.map(|b| if *b == b'\n' { b' ' } else { *b })); events.push(Event::Comment(parse::Comment { tag: b'#', text: Span::append(self.backing, &c)?, })); } if self.implicit_newline { events.push(Event::Newline(Span::append(self.backing, &self.newline)?)); } self.section.body.0.extend(events); Ok(()) } /// Removes all events until a key value pair is removed. This will also /// remove the whitespace preceding the key value pair, if any is found. pub fn pop(&mut self) -> Option<(String, BString)> { let mut values: Vec<BString> = Vec::new(); // events are popped in reverse order let body = &mut self.section.body.0; while let Some(e) = body.pop() { match e { Event::SectionValueName(k) => { // pop leading whitespace if let Some(Event::Whitespace(_)) = body.last() { body.pop(); } if values.len() == 1 { let value = values.pop().expect("vec is non-empty but popped to empty value"); return Some(( k.as_bstr_in(self.backing) .to_str() .expect("parsed value names are ASCII") .to_owned(), normalize(&value).into_owned(), )); } return Some(( k.as_bstr_in(self.backing) .to_str() .expect("parsed value names are ASCII") .to_owned(), normalize(&{ let mut s = BString::default(); for value in values.into_iter().rev() { s.push_str(value.as_slice()); } s }) .into_owned(), )); } Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => { values.push(v.to_bstring_in(self.backing)); } _ => (), } } None } /// Sets the last key value pair if it exists, or adds the new value. /// Returns the previous value if it replaced a value, or None if it adds /// the value. pub fn set( &mut self, value_name: impl AsRef<str>, value: impl crate::AsBStr, ) -> Result<Option<BString>, file::section::value::Error> { let value_name = ValueName::try_from(value_name.as_ref())?; self.set_inner(value_name, value.as_bstr()).map_err(Into::into) } pub(crate) fn set_inner( &mut self, value_name: ValueName, value: &BStr, ) -> Result<Option<BString>, parse::span::Error> { match self.section.body.key_and_value_range_by_in(self.backing, &value_name) { None => { self.push_with_comment_inner(value_name, Some(value), None)?; Ok(None) } Some((key_range, value_range)) => { let value_range = value_range.unwrap_or(key_range.end - 1..key_range.end); let range_start = value_range.start; let value = Span::append(self.backing, &escape_value(value))?; let ret = self.remove_internal(value_range, false); self.section.body.0.insert(range_start, Event::Value(value)); Ok(Some(ret)) } } } /// Set the trust level in the meta-data of this section to `trust`. pub fn set_trust(&mut self, trust: Trust) -> &mut Self { let mut meta = (*self.section.meta).clone(); meta.trust = trust; self.section.meta = meta.into(); self } /// Removes the latest value by key and returns it, if it exists. pub fn remove(&mut self, value_name: &str) -> Option<BString> { let key = ValueName::from_str_unchecked(value_name); let (key_range, _value_range) = self.section.body.key_and_value_range_by_in(self.backing, &key)?; Some(self.remove_internal(key_range, true)) } /// Adds a new line event. Note that you don't need to call this unless /// you've disabled implicit newlines. pub fn push_newline(&mut self) -> Result<&mut Self, parse::span::Error> { let newline = Span::append(self.backing, &self.newline)?; self.section.body.0.push(Event::Newline(newline)); Ok(self) } /// Return the newline used when calling [`push_newline()`][Self::push_newline()]. pub fn newline(&self) -> &BStr { self.newline.as_slice().as_bstr() } /// Enables or disables automatically adding newline events after adding /// a value. This is _enabled by default_. pub fn set_implicit_newline(&mut self, on: bool) -> &mut Self { self.implicit_newline = on; self } /// Sets the exact `whitespace` to use before each newly created key-value pair, /// with only whitespace characters being permissible. /// /// The default is 2 tabs. /// Set to `None` to disable adding whitespace before a key value. /// /// # Panics /// /// If non-whitespace characters are used. This makes the method only suitable for validated /// or known input. // TODO(error): make it fallible pub fn set_leading_whitespace(&mut self, whitespace: impl IntoBStringOpt) -> &mut Self { let whitespace = whitespace.into_bstring_opt(); assert!( whitespace .as_deref() .is_none_or(|ws| ws.iter().all(u8::is_ascii_whitespace)), "input whitespace must only contain whitespace characters." ); self.whitespace.pre_key = whitespace; self } /// Returns the whitespace this section will insert before the /// beginning of a key, if any. #[must_use] pub fn leading_whitespace(&self) -> Option<&BStr> { self.whitespace.pre_key.as_ref().map(|v| v.as_slice().as_bstr()) } /// Return the immutable section view backing this mutable handle. pub fn section(&self) -> SectionRef<'_> { SectionRef::from_data(self.section, self.backing) } /// Return the header of the section backing this mutable handle. pub fn header(&self) -> file::section::HeaderRef<'_> { file::section::HeaderRef { header: &self.section.header, backing: self.backing, } } /// Return the unique `id` of the section backing this mutable handle. pub fn id(&self) -> file::SectionId { self.section.id } /// Return the section body backing this mutable handle. pub fn body(&self) -> file::section::BodyRef<'_> { file::section::BodyRef { body: &self.section.body, backing: self.backing, } } /// Serialize the section backing this mutable handle into a `BString`. #[must_use] pub fn to_bstring(&self) -> BString { self.section().to_bstring() } /// Return additional information about this section's origin. pub fn meta(&self) -> &file::Metadata { &self.section.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.section .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.section.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.section.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.section.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.section.body.contains_value_name_in(self.backing, value_name) } /// Returns the number of values in the section. #[must_use] pub fn num_values(&self) -> usize { self.section.body.num_values() } /// Returns if the section is empty. #[must_use] pub fn is_void(&self) -> bool { self.section.body.is_void() } /// Returns the whitespace to be used before and after the `=` between the key /// and the value. /// /// For example, `k = v` will have `(Some(" "), Some(" "))`, whereas `k=\tv` will /// have `(None, Some("\t"))`. #[must_use] pub fn separator_whitespace(&self) -> (Option<&BStr>, Option<&BStr>) { ( self.whitespace.pre_sep.as_ref().map(|v| v.as_slice().as_bstr()), self.whitespace.post_sep.as_ref().map(|v| v.as_slice().as_bstr()), ) }} // Internal methods that may require exact indices for faster operations.impl<'a> SectionMut<'a> { pub(crate) fn new( section: &'a mut SectionData, backing: &'a mut Vec<u8>, lookup: Option<LookupMut<'a>>, newline: SmallVec<[u8; 2]>, ) -> Self { let whitespace = Whitespace::from_body(§ion.body, backing); Self { section, backing, lookup, implicit_newline: true, whitespace, newline, } } pub(crate) fn set_header(&mut self, header: parse::section::HeaderData) { if let Some(lookup) = &mut self.lookup { crate::file::util::set_section_header(self.section, self.backing, lookup.tree, lookup.order, header); } else { self.section.header = header; } } pub(crate) fn get(&self, key: &ValueName, start: Index, end: Index) -> Result<BString, lookup::existing::Error> { let mut expect_value = false; let mut concatenated_value = BString::default(); for event in &self.section.body.0[start.0..end.0] { match event { Event::SectionValueName(event_key) if event_key .as_bstr_in(self.backing) .eq_ignore_ascii_case(key.0.as_slice()) => { expect_value = true; } Event::Value(v) if expect_value => return Ok(normalize(v.as_slice_in(self.backing)).into_owned()), Event::ValueNotDone(v) if expect_value => { concatenated_value.push_str(v.as_slice_in(self.backing)); } Event::ValueDone(v) if expect_value => { concatenated_value.push_str(v.as_slice_in(self.backing)); return Ok(normalize(&concatenated_value).into_owned()); } _ => (), } } Err(lookup::existing::Error::KeyMissing) } pub(crate) fn delete(&mut self, start: Index, end: Index) { self.section.body.0.drain(start.0..end.0); } pub(crate) fn set_internal( &mut self, index: Index, key: ValueName, value: &BStr, ) -> Result<Size, parse::span::Error> { let mut size = 0; let value = Span::append(self.backing, &escape_value(value))?; let sep_events = self.whitespace.key_value_separators(self.backing)?; let key = Span::append(self.backing, key.0.as_slice())?; let body = &mut self.section.body.0; body.insert(index.0, Event::Value(value)); size += 1; size += sep_events.len(); body.splice(index.0..index.0, sep_events.into_iter().rev()) .for_each(|_| {}); body.insert(index.0, Event::SectionValueName(key)); size += 1; Ok(Size(size)) } /// Performs the removal, assuming the range is valid. fn remove_internal(&mut self, range: Range<usize>, fix_whitespace: bool) -> BString { let events = &mut self.section.body.0; if fix_whitespace && events.get(range.end).is_some_and(|ev| matches!(ev, Event::Newline(_))) { events.remove(range.end); } let value = events.drain(range.clone()).fold(BString::default(), |mut acc, e| { if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e { acc.push_str(v.as_slice_in(self.backing)); } acc }); if fix_whitespace && range .start .checked_sub(1) .and_then(|pos| events.get(pos)) .is_some_and(|ev| matches!(ev, Event::Whitespace(_))) { events.remove(range.start - 1); } value }} impl file::section::BodyData { pub(crate) fn as_mut(&mut self) -> &mut Vec<Event> { &mut self.0 }}
use std::{collections::HashMap, ops::Range};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use gix_sec::Trust;
use smallvec::SmallVec;
use crate::{
AsBStrOpt,
file::{
self, Index, IntoBStringOpt, SectionData, SectionId, SectionLookup, SectionRef, Size,
mutable::{Whitespace, escape_value},
},
lookup,
parse::{self, Event, Span, section::ValueName},
value::normalize,
};
/// A opaque type that represents a mutable reference to a section.
#[derive(Debug)]
pub struct SectionMut<'a> {
section: &'a mut SectionData,
backing: &'a mut Vec<u8>,
/// Present only if backed by [`crate::File`]
lookup: Option<LookupMut<'a>>,
implicit_newline: bool,
whitespace: Whitespace,
newline: SmallVec<[u8; 2]>,
}
#[derive(Debug)]
pub(crate) struct LookupMut<'a> {
pub(crate) tree: &'a mut HashMap<parse::section::Name, SectionLookup>,
pub(crate) order: &'a [SectionId],
}
/// Mutating methods.
impl SectionMut<'_> {
/// Rename this section to `name` and the optional `subsection_name`.
///
/// Section names are not unique, and renaming into an existing section name is permitted.
pub fn rename(
&mut self,
name: impl AsRef<str>,
subsection_name: impl IntoBStringOpt,
) -> Result<&mut Self, parse::section::header::Error> {
let header = parse::section::HeaderData::new_in(name, subsection_name.into_bstring_opt(), self.backing)?;
self.set_header(header);
Ok(self)
}
/// Adds an entry to the end of this section name `value_name` and `value`. If `value` is `None`, no equal sign will be written leaving
/// just the key. This is useful for boolean values which are true if merely the key exists.
pub fn push(
&mut self,
value_name: impl AsRef<str>,
value: impl AsBStrOpt,
) -> Result<&mut Self, file::section::value::Error> {
let value_name = ValueName::try_from(value_name.as_ref())?;
self.push_with_comment_inner(value_name, value.as_bstr_opt(), None)?;
Ok(self)
}
/// Adds an entry to the end of this section name `value_name` and `value`. If `value` is `None`, no equal sign will be written leaving
/// just the key. This is useful for boolean values which are true if merely the key exists.
/// `comment` has to be the text to put right after the value and behind a `#` character. Note that newlines are silently transformed
/// into spaces.
pub fn push_with_comment(
&mut self,
value_name: impl AsRef<str>,
value: impl AsBStrOpt,
comment: impl crate::AsBStr,
) -> Result<&mut Self, file::section::value::Error> {
let value_name = ValueName::try_from(value_name.as_ref())?;
self.push_with_comment_inner(value_name, value.as_bstr_opt(), Some(comment.as_bstr()))?;
Ok(self)
}
fn push_with_comment_inner(
&mut self,
value_name: ValueName,
value: Option<&BStr>,
comment: Option<&BStr>,
) -> Result<(), parse::span::Error> {
let mut events = Vec::new();
if let Some(ws) = &self.whitespace.pre_key {
events.push(Event::Whitespace(Span::append(self.backing, ws)?));
}
events.push(Event::SectionValueName(Span::append(
self.backing,
value_name.0.as_slice(),
)?));
match value {
Some(value) => {
events.extend(self.whitespace.key_value_separators(self.backing)?);
events.push(Event::Value(Span::append(self.backing, &escape_value(value))?));
}
None => events.push(Event::Value(Span::append(self.backing, b"")?)),
}
if let Some(comment) = comment {
events.push(Event::Whitespace(Span::append(self.backing, b" ")?));
let mut c = Vec::with_capacity(comment.len());
let mut bytes = comment.iter().peekable();
if !bytes.peek().is_none_or(|b| b.is_ascii_whitespace()) {
c.insert(0, b' ');
}
c.extend(bytes.map(|b| if *b == b'\n' { b' ' } else { *b }));
events.push(Event::Comment(parse::Comment {
tag: b'#',
text: Span::append(self.backing, &c)?,
}));
}
if self.implicit_newline {
events.push(Event::Newline(Span::append(self.backing, &self.newline)?));
}
self.section.body.0.extend(events);
Ok(())
}
/// Removes all events until a key value pair is removed. This will also
/// remove the whitespace preceding the key value pair, if any is found.
pub fn pop(&mut self) -> Option<(String, BString)> {
let mut values: Vec<BString> = Vec::new();
// events are popped in reverse order
let body = &mut self.section.body.0;
while let Some(e) = body.pop() {
match e {
Event::SectionValueName(k) => {
// pop leading whitespace
if let Some(Event::Whitespace(_)) = body.last() {
body.pop();
}
if values.len() == 1 {
let value = values.pop().expect("vec is non-empty but popped to empty value");
return Some((
k.as_bstr_in(self.backing)
.to_str()
.expect("parsed value names are ASCII")
.to_owned(),
normalize(&value).into_owned(),
));
}
return Some((
k.as_bstr_in(self.backing)
.to_str()
.expect("parsed value names are ASCII")
.to_owned(),
normalize(&{
let mut s = BString::default();
for value in values.into_iter().rev() {
s.push_str(value.as_slice());
}
s
})
.into_owned(),
));
}
Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => {
values.push(v.to_bstring_in(self.backing));
}
_ => (),
}
}
None
}
/// Sets the last key value pair if it exists, or adds the new value.
/// Returns the previous value if it replaced a value, or None if it adds
/// the value.
pub fn set(
&mut self,
value_name: impl AsRef<str>,
value: impl crate::AsBStr,
) -> Result<Option<BString>, file::section::value::Error> {
let value_name = ValueName::try_from(value_name.as_ref())?;
self.set_inner(value_name, value.as_bstr()).map_err(Into::into)
}
pub(crate) fn set_inner(
&mut self,
value_name: ValueName,
value: &BStr,
) -> Result<Option<BString>, parse::span::Error> {
match self.section.body.key_and_value_range_by_in(self.backing, &value_name) {
None => {
self.push_with_comment_inner(value_name, Some(value), None)?;
Ok(None)
}
Some((key_range, value_range)) => {
let value_range = value_range.unwrap_or(key_range.end - 1..key_range.end);
let range_start = value_range.start;
let value = Span::append(self.backing, &escape_value(value))?;
let ret = self.remove_internal(value_range, false);
self.section.body.0.insert(range_start, Event::Value(value));
Ok(Some(ret))
}
}
}
/// Set the trust level in the meta-data of this section to `trust`.
pub fn set_trust(&mut self, trust: Trust) -> &mut Self {
let mut meta = (*self.section.meta).clone();
meta.trust = trust;
self.section.meta = meta.into();
self
}
/// Removes the latest value by key and returns it, if it exists.
pub fn remove(&mut self, value_name: &str) -> Option<BString> {
let key = ValueName::from_str_unchecked(value_name);
let (key_range, _value_range) = self.section.body.key_and_value_range_by_in(self.backing, &key)?;
Some(self.remove_internal(key_range, true))
}
/// Adds a new line event. Note that you don't need to call this unless
/// you've disabled implicit newlines.
pub fn push_newline(&mut self) -> Result<&mut Self, parse::span::Error> {
let newline = Span::append(self.backing, &self.newline)?;
self.section.body.0.push(Event::Newline(newline));
Ok(self)
}
/// Return the newline used when calling [`push_newline()`][Self::push_newline()].
pub fn newline(&self) -> &BStr {
self.newline.as_slice().as_bstr()
}
/// Enables or disables automatically adding newline events after adding
/// a value. This is _enabled by default_.
pub fn set_implicit_newline(&mut self, on: bool) -> &mut Self {
self.implicit_newline = on;
self
}
/// Sets the exact `whitespace` to use before each newly created key-value pair,
/// with only whitespace characters being permissible.
///
/// The default is 2 tabs.
/// Set to `None` to disable adding whitespace before a key value.
///
/// # Panics
///
/// If non-whitespace characters are used. This makes the method only suitable for validated
/// or known input.
// TODO(error): make it fallible
pub fn set_leading_whitespace(&mut self, whitespace: impl IntoBStringOpt) -> &mut Self {
let whitespace = whitespace.into_bstring_opt();
assert!(
whitespace
.as_deref()
.is_none_or(|ws| ws.iter().all(u8::is_ascii_whitespace)),
"input whitespace must only contain whitespace characters."
);
self.whitespace.pre_key = whitespace;
self
}
/// Returns the whitespace this section will insert before the
/// beginning of a key, if any.
#[must_use]
pub fn leading_whitespace(&self) -> Option<&BStr> {
self.whitespace.pre_key.as_ref().map(|v| v.as_slice().as_bstr())
}
/// Return the immutable section view backing this mutable handle.
pub fn section(&self) -> SectionRef<'_> {
SectionRef::from_data(self.section, self.backing)
}
/// Return the header of the section backing this mutable handle.
pub fn header(&self) -> file::section::HeaderRef<'_> {
file::section::HeaderRef {
header: &self.section.header,
backing: self.backing,
}
}
/// Return the unique `id` of the section backing this mutable handle.
pub fn id(&self) -> file::SectionId {
self.section.id
}
/// Return the section body backing this mutable handle.
pub fn body(&self) -> file::section::BodyRef<'_> {
file::section::BodyRef {
body: &self.section.body,
backing: self.backing,
}
}
/// Serialize the section backing this mutable handle into a `BString`.
#[must_use]
pub fn to_bstring(&self) -> BString {
self.section().to_bstring()
}
/// Return additional information about this section's origin.
pub fn meta(&self) -> &file::Metadata {
&self.section.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.section
.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.section.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.section.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.section.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.section.body.contains_value_name_in(self.backing, value_name)
}
/// Returns the number of values in the section.
#[must_use]
pub fn num_values(&self) -> usize {
self.section.body.num_values()
}
/// Returns if the section is empty.
#[must_use]
pub fn is_void(&self) -> bool {
self.section.body.is_void()
}
/// Returns the whitespace to be used before and after the `=` between the key
/// and the value.
///
/// For example, `k = v` will have `(Some(" "), Some(" "))`, whereas `k=\tv` will
/// have `(None, Some("\t"))`.
#[must_use]
pub fn separator_whitespace(&self) -> (Option<&BStr>, Option<&BStr>) {
(
self.whitespace.pre_sep.as_ref().map(|v| v.as_slice().as_bstr()),
self.whitespace.post_sep.as_ref().map(|v| v.as_slice().as_bstr()),
)
}
}
// Internal methods that may require exact indices for faster operations.
impl<'a> SectionMut<'a> {
pub(crate) fn new(
section: &'a mut SectionData,
backing: &'a mut Vec<u8>,
lookup: Option<LookupMut<'a>>,
newline: SmallVec<[u8; 2]>,
) -> Self {
let whitespace = Whitespace::from_body(§ion.body, backing);
Self {
section,
backing,
lookup,
implicit_newline: true,
whitespace,
newline,
}
}
pub(crate) fn set_header(&mut self, header: parse::section::HeaderData) {
if let Some(lookup) = &mut self.lookup {
crate::file::util::set_section_header(self.section, self.backing, lookup.tree, lookup.order, header);
} else {
self.section.header = header;
}
}
pub(crate) fn get(&self, key: &ValueName, start: Index, end: Index) -> Result<BString, lookup::existing::Error> {
let mut expect_value = false;
let mut concatenated_value = BString::default();
for event in &self.section.body.0[start.0..end.0] {
match event {
Event::SectionValueName(event_key)
if event_key
.as_bstr_in(self.backing)
.eq_ignore_ascii_case(key.0.as_slice()) =>
{
expect_value = true;
}
Event::Value(v) if expect_value => return Ok(normalize(v.as_slice_in(self.backing)).into_owned()),
Event::ValueNotDone(v) if expect_value => {
concatenated_value.push_str(v.as_slice_in(self.backing));
}
Event::ValueDone(v) if expect_value => {
concatenated_value.push_str(v.as_slice_in(self.backing));
return Ok(normalize(&concatenated_value).into_owned());
}
_ => (),
}
}
Err(lookup::existing::Error::KeyMissing)
}
pub(crate) fn delete(&mut self, start: Index, end: Index) {
self.section.body.0.drain(start.0..end.0);
}
pub(crate) fn set_internal(
&mut self,
index: Index,
key: ValueName,
value: &BStr,
) -> Result<Size, parse::span::Error> {
let mut size = 0;
let value = Span::append(self.backing, &escape_value(value))?;
let sep_events = self.whitespace.key_value_separators(self.backing)?;
let key = Span::append(self.backing, key.0.as_slice())?;
let body = &mut self.section.body.0;
body.insert(index.0, Event::Value(value));
size += 1;
size += sep_events.len();
body.splice(index.0..index.0, sep_events.into_iter().rev())
.for_each(|_| {});
body.insert(index.0, Event::SectionValueName(key));
size += 1;
Ok(Size(size))
}
/// Performs the removal, assuming the range is valid.
fn remove_internal(&mut self, range: Range<usize>, fix_whitespace: bool) -> BString {
let events = &mut self.section.body.0;
if fix_whitespace && events.get(range.end).is_some_and(|ev| matches!(ev, Event::Newline(_))) {
events.remove(range.end);
}
let value = events.drain(range.clone()).fold(BString::default(), |mut acc, e| {
if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e {
acc.push_str(v.as_slice_in(self.backing));
}
acc
});
if fix_whitespace
&& range
.start
.checked_sub(1)
.and_then(|pos| events.get(pos))
.is_some_and(|ev| matches!(ev, Event::Whitespace(_)))
{
events.remove(range.start - 1);
}
value
}
}
impl file::section::BodyData {
pub(crate) fn as_mut(&mut self) -> &mut Vec<Event> {
&mut self.0
}
}