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,
};
#[derive(Debug)]
pub struct SectionMut<'a> {
section: &'a mut SectionData,
backing: &'a mut Vec<u8>,
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],
}
impl SectionMut<'_> {
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)
}
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)
}
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(())
}
pub fn pop(&mut self) -> Option<(String, BString)> {
let mut values: Vec<BString> = Vec::new();
let body = &mut self.section.body.0;
while let Some(e) = body.pop() {
match e {
Event::SectionValueName(k) => {
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
}
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))
}
}
}
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
}
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))
}
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)
}
pub fn newline(&self) -> &BStr {
self.newline.as_slice().as_bstr()
}
pub fn set_implicit_newline(&mut self, on: bool) -> &mut Self {
self.implicit_newline = on;
self
}
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
}
#[must_use]
pub fn leading_whitespace(&self) -> Option<&BStr> {
self.whitespace.pre_key.as_ref().map(|v| v.as_slice().as_bstr())
}
pub fn section(&self) -> SectionRef<'_> {
SectionRef::from_data(self.section, self.backing)
}
pub fn header(&self) -> file::section::HeaderRef<'_> {
file::section::HeaderRef {
header: &self.section.header,
backing: self.backing,
}
}
pub fn id(&self) -> file::SectionId {
self.section.id
}
pub fn body(&self) -> file::section::BodyRef<'_> {
file::section::BodyRef {
body: &self.section.body,
backing: self.backing,
}
}
#[must_use]
pub fn to_bstring(&self) -> BString {
self.section().to_bstring()
}
pub fn meta(&self) -> &file::Metadata {
&self.section.meta
}
#[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()
}
#[must_use]
pub fn value_implicit(&self, value_name: &str) -> Option<Option<BString>> {
self.section.body.value_implicit_in(self.backing, value_name)
}
#[must_use]
pub fn values(&self, value_name: &str) -> Vec<BString> {
self.section.body.values_in(self.backing, value_name)
}
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,
})
}
#[must_use]
pub fn contains_value_name(&self, value_name: &str) -> bool {
self.section.body.contains_value_name_in(self.backing, value_name)
}
#[must_use]
pub fn num_values(&self) -> usize {
self.section.body.num_values()
}
#[must_use]
pub fn is_void(&self) -> bool {
self.section.body.is_void()
}
#[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()),
)
}
}
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))
}
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
}
}