diff options
Diffstat (limited to 'vendor/gix-config/src/file/mutable/multi_value.rs')
-rw-r--r-- | vendor/gix-config/src/file/mutable/multi_value.rs | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/vendor/gix-config/src/file/mutable/multi_value.rs b/vendor/gix-config/src/file/mutable/multi_value.rs new file mode 100644 index 000000000..396b49b6a --- /dev/null +++ b/vendor/gix-config/src/file/mutable/multi_value.rs @@ -0,0 +1,266 @@ +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<SectionId, Section<'event>>, + 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<EntryData>, + /// 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<SectionId, Vec<usize>>, +} + +impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { + /// Returns the actual values. + pub fn get(&self) -> Result<Vec<Cow<'_, BStr>>, 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<str>) { + 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 = Item>, + 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<SectionId, Vec<usize>>, + 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<SectionId, Vec<usize>>, + 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<SectionId, Vec<usize>>, + 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; + } +} |