blob: 7c6089f4795f24395d194e6b2d572512a98e1fe3 [file] [log] [blame]
use std::{
borrow::Cow,
ops::{Deref, Range},
};
use bstr::{BStr, BString, ByteSlice, ByteVec};
use smallvec::SmallVec;
use crate::{
file::{
self,
mutable::{escape_value, Whitespace},
Index, Section, Size,
},
lookup, parse,
parse::{section::Key, Event},
value::{normalize, normalize_bstr, normalize_bstring},
};
/// A opaque type that represents a mutable reference to a section.
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct SectionMut<'a, 'event> {
section: &'a mut Section<'event>,
implicit_newline: bool,
whitespace: Whitespace<'event>,
newline: SmallVec<[u8; 2]>,
}
/// Mutating methods.
impl<'a, 'event> SectionMut<'a, 'event> {
/// Adds an entry to the end of this section name `key` 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<'b>(&mut self, key: Key<'event>, value: Option<&'b BStr>) -> &mut Self {
self.push_with_comment_inner(key, value, None);
self
}
/// Adds an entry to the end of this section name `key` 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<'b, 'c>(
&mut self,
key: Key<'event>,
value: Option<&'b BStr>,
comment: impl Into<&'c BStr>,
) -> &mut Self {
self.push_with_comment_inner(key, value, comment.into().into());
self
}
fn push_with_comment_inner(&mut self, key: Key<'event>, value: Option<&BStr>, comment: Option<&BStr>) {
let body = &mut self.section.body.0;
if let Some(ws) = &self.whitespace.pre_key {
body.push(Event::Whitespace(ws.clone()));
}
body.push(Event::SectionKey(key));
match value {
Some(value) => {
body.extend(self.whitespace.key_value_separators());
body.push(Event::Value(escape_value(value).into()));
}
None => body.push(Event::Value(Cow::Borrowed("".into()))),
}
if let Some(comment) = comment {
body.push(Event::Whitespace(Cow::Borrowed(" ".into())));
body.push(Event::Comment(parse::Comment {
tag: b'#',
text: Cow::Owned({
let mut c = Vec::with_capacity(comment.len());
let mut bytes = comment.iter().peekable();
if !bytes.peek().map_or(true, |b| b.is_ascii_whitespace()) {
c.insert(0, b' ');
}
c.extend(bytes.map(|b| if *b == b'\n' { b' ' } else { *b }));
c.into()
}),
}));
}
if self.implicit_newline {
body.push(Event::Newline(BString::from(self.newline.to_vec()).into()));
}
}
/// 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<(Key<'_>, Cow<'event, BStr>)> {
let mut values = Vec::new();
// events are popped in reverse order
let body = &mut self.section.body.0;
while let Some(e) = body.pop() {
match e {
Event::SectionKey(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, normalize(value)));
}
return Some((
k,
normalize_bstring({
let mut s = BString::default();
for value in values.into_iter().rev() {
s.push_str(value.as_ref());
}
s
}),
));
}
Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => values.push(v),
_ => (),
}
}
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, key: Key<'event>, value: &BStr) -> Option<Cow<'event, BStr>> {
match self.key_and_value_range_by(&key) {
None => {
self.push(key, Some(value));
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 ret = self.remove_internal(value_range, false);
self.section
.body
.0
.insert(range_start, Event::Value(escape_value(value).into()));
Some(ret)
}
}
}
/// Removes the latest value by key and returns it, if it exists.
pub fn remove(&mut self, key: &str) -> Option<Cow<'event, BStr>> {
let key = Key::from_str_unchecked(key);
let (key_range, _value_range) = self.key_and_value_range_by(&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) -> &mut Self {
self.section
.body
.0
.push(Event::Newline(Cow::Owned(BString::from(self.newline.to_vec()))));
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.
pub fn set_leading_whitespace(&mut self, whitespace: Option<Cow<'event, BStr>>) -> &mut Self {
assert!(
whitespace
.as_deref()
.map_or(true, |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_deref()
}
/// 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_deref(), self.whitespace.post_sep.as_deref())
}
}
// Internal methods that may require exact indices for faster operations.
impl<'a, 'event> SectionMut<'a, 'event> {
pub(crate) fn new(section: &'a mut Section<'event>, newline: SmallVec<[u8; 2]>) -> Self {
let whitespace = Whitespace::from_body(&section.body);
Self {
section,
implicit_newline: true,
whitespace,
newline,
}
}
pub(crate) fn get(
&self,
key: &Key<'_>,
start: Index,
end: Index,
) -> Result<Cow<'_, BStr>, lookup::existing::Error> {
let mut expect_value = false;
let mut concatenated_value = BString::default();
for event in &self.section.0[start.0..end.0] {
match event {
Event::SectionKey(event_key) if event_key == key => expect_value = true,
Event::Value(v) if expect_value => return Ok(normalize_bstr(v.as_ref())),
Event::ValueNotDone(v) if expect_value => {
concatenated_value.push_str(v.as_ref());
}
Event::ValueDone(v) if expect_value => {
concatenated_value.push_str(v.as_ref());
return Ok(normalize_bstring(concatenated_value));
}
_ => (),
}
}
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: Key<'event>, value: &BStr) -> Size {
let mut size = 0;
let body = &mut self.section.body.0;
body.insert(index.0, Event::Value(escape_value(value).into()));
size += 1;
let sep_events = self.whitespace.key_value_separators();
size += sep_events.len();
body.splice(index.0..index.0, sep_events.into_iter().rev())
.for_each(|_| {});
body.insert(index.0, Event::SectionKey(key));
size += 1;
Size(size)
}
/// Performs the removal, assuming the range is valid.
fn remove_internal(&mut self, range: Range<usize>, fix_whitespace: bool) -> Cow<'event, BStr> {
let events = &mut self.section.body.0;
if fix_whitespace
&& events
.get(range.end)
.map_or(false, |ev| matches!(ev, Event::Newline(_)))
{
events.remove(range.end);
}
let value = events
.drain(range.clone())
.fold(Cow::Owned(BString::default()), |mut acc: Cow<'_, BStr>, e| {
if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e {
acc.to_mut().extend(&**v);
}
acc
});
if fix_whitespace
&& range
.start
.checked_sub(1)
.and_then(|pos| events.get(pos))
.map_or(false, |ev| matches!(ev, Event::Whitespace(_)))
{
events.remove(range.start - 1);
}
value
}
}
impl<'event> Deref for SectionMut<'_, 'event> {
type Target = file::Section<'event>;
fn deref(&self) -> &Self::Target {
self.section
}
}
impl<'event> file::section::Body<'event> {
pub(crate) fn as_mut(&mut self) -> &mut Vec<Event<'event>> {
&mut self.0
}
}