use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; use bstr::{BStr, BString, ByteVec}; use crate::{ file::{ self, mutable::{escape_value, Whitespace}, Section, SectionId, }, lookup, parse::{section, Event}, value::{normalize_bstr, normalize_bstring}, }; /// Internal data structure for [`MutableMultiValue`] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub(crate) struct EntryData { pub(crate) section_id: SectionId, pub(crate) offset_index: usize, } /// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. #[derive(PartialEq, Eq, Debug)] pub struct MultiValueMut<'borrow, 'lookup, 'event> { pub(crate) section: &'borrow mut HashMap>, pub(crate) key: section::Key<'lookup>, /// Each entry data struct provides sufficient information to index into /// [`Self::offsets`]. This layer of indirection is used for users to index /// into the offsets rather than leaking the internal data structures. pub(crate) indices_and_sizes: Vec, /// Each offset represents the size of a event slice and whether or not the /// event slice is significant or not. This is used to index into the /// actual section. pub(crate) offsets: HashMap>, } impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { /// Returns the actual values. pub fn get(&self) -> Result>, lookup::existing::Error> { let mut expect_value = false; let mut values = Vec::new(); let mut concatenated_value = BString::default(); for EntryData { section_id, offset_index, } in &self.indices_and_sizes { let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); for event in &self.section.get(section_id).expect("known section id").as_ref()[offset..offset + size] { match event { Event::SectionKey(section_key) if *section_key == self.key => expect_value = true, Event::Value(v) if expect_value => { expect_value = false; values.push(normalize_bstr(v.as_ref())); } Event::ValueNotDone(v) if expect_value => concatenated_value.push_str(v.as_ref()), Event::ValueDone(v) if expect_value => { expect_value = false; concatenated_value.push_str(v.as_ref()); values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); } _ => (), } } } if values.is_empty() { return Err(lookup::existing::Error::KeyMissing); } Ok(values) } /// Returns the amount of values within this multivar. #[must_use] pub fn len(&self) -> usize { self.indices_and_sizes.len() } /// Returns true if the multivar does not have any values. /// This might occur if the value was deleted but wasn't yet set with a new value. #[must_use] pub fn is_empty(&self) -> bool { self.indices_and_sizes.is_empty() } /// Sets the value at the given index. /// /// # Safety /// /// This will panic if the index is out of range. pub fn set_string_at(&mut self, index: usize, value: impl AsRef) { self.set_at(index, value.as_ref()); } /// Sets the value at the given index. /// /// # Safety /// /// This will panic if the index is out of range. pub fn set_at<'a>(&mut self, index: usize, value: impl Into<&'a BStr>) { let EntryData { section_id, offset_index, } = self.indices_and_sizes[index]; MultiValueMut::set_value_inner( &self.key, &mut self.offsets, &mut self.section.get_mut(§ion_id).expect("known section id").body, section_id, offset_index, value.into(), ); } /// Sets all values to the provided ones. Note that this follows [`zip`] /// logic: if the number of values in the input is less than the number of /// values currently existing, then only the first `n` values are modified. /// If more values are provided than there currently are, then the /// remaining values are ignored. /// /// [`zip`]: std::iter::Iterator::zip pub fn set_values<'a, Iter, Item>(&mut self, values: Iter) where Iter: IntoIterator, Item: Into<&'a BStr>, { for ( EntryData { section_id, offset_index, }, value, ) in self.indices_and_sizes.iter().zip(values) { Self::set_value_inner( &self.key, &mut self.offsets, &mut self.section.get_mut(section_id).expect("known section id").body, *section_id, *offset_index, value.into(), ); } } /// Sets all values in this multivar to the provided one without owning the /// provided input. pub fn set_all<'a>(&mut self, input: impl Into<&'a BStr>) { let input = input.into(); for EntryData { section_id, offset_index, } in &self.indices_and_sizes { Self::set_value_inner( &self.key, &mut self.offsets, &mut self.section.get_mut(section_id).expect("known section id").body, *section_id, *offset_index, input, ); } } fn set_value_inner<'a: 'event>( key: §ion::Key<'lookup>, offsets: &mut HashMap>, section: &mut file::section::Body<'event>, section_id: SectionId, offset_index: usize, value: &BStr, ) { let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index); let whitespace = Whitespace::from_body(section); let section = section.as_mut(); section.drain(offset..offset + size); let key_sep_events = whitespace.key_value_separators(); MultiValueMut::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len()); section.insert(offset, Event::Value(escape_value(value).into())); section.insert_many(offset, key_sep_events.into_iter().rev()); section.insert(offset, Event::SectionKey(key.to_owned())); } /// Removes the value at the given index. Does nothing when called multiple /// times in succession. /// /// # Safety /// /// This will panic if the index is out of range. pub fn delete(&mut self, index: usize) { let EntryData { section_id, offset_index, } = &self.indices_and_sizes[index]; let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); if size == 0 { return; } self.section .get_mut(section_id) .expect("known section id") .body .as_mut() .drain(offset..offset + size); Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); self.indices_and_sizes.remove(index); } /// Removes all values. Does nothing when called multiple times in /// succession. pub fn delete_all(&mut self) { for EntryData { section_id, offset_index, } in &self.indices_and_sizes { let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index); if size == 0 { continue; } self.section .get_mut(section_id) .expect("known section id") .body .as_mut() .drain(offset..offset + size); Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); } self.indices_and_sizes.clear(); } fn index_and_size( offsets: &'lookup HashMap>, section_id: SectionId, offset_index: usize, ) -> (usize, usize) { offsets .get(§ion_id) .expect("known section id") .iter() .take(offset_index + 1) .fold((0, 0), |(total_ofs, ofs), size| (total_ofs + ofs, *size)) } // This must be an associated function rather than a method to allow Rust // to split mutable borrows. fn set_offset( offsets: &mut HashMap>, section_id: SectionId, offset_index: usize, value: usize, ) { *offsets .get_mut(§ion_id) .expect("known section id") .get_mut(offset_index) .unwrap() .deref_mut() = value; } }