gix-config 0.59.0

A git-config file parser and editor from the gitoxide project
Documentation
use std::{collections::HashMap, ops::DerefMut};

use bstr::{BStr, BString, ByteVec};

use crate::{
    file::{
        self, SectionData, SectionId,
        mutable::{Whitespace, escape_value},
    },
    lookup,
    parse::{Event, section},
    value::normalize,
};

/// 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(Debug)]
pub struct MultiValueMut<'borrow> {
    pub(crate) section: &'borrow mut HashMap<SectionId, SectionData>,
    pub(crate) backing: &'borrow mut Vec<u8>,
    pub(crate) key: section::ValueName,
    /// 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 MultiValueMut<'_> {
    /// Returns the actual values.
    pub fn get(&self) -> Result<Vec<BString>, 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::SectionValueName(section_key)
                        if section_key
                            .as_bstr_in(self.backing)
                            .eq_ignore_ascii_case(self.key.0.as_slice()) =>
                    {
                        expect_value = true;
                    }
                    Event::Value(v) if expect_value => {
                        expect_value = false;
                        values.push(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 => {
                        expect_value = false;
                        concatenated_value.push_str(v.as_slice_in(self.backing));
                        let concatenated_value = std::mem::take(&mut concatenated_value);
                        values.push(normalize(&concatenated_value).into_owned());
                    }
                    _ => (),
                }
            }
        }

        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>) -> Result<(), crate::parse::span::Error> {
        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(&mut self, index: usize, value: impl crate::AsBStr) -> Result<(), crate::parse::span::Error> {
        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(&section_id).expect("known section id").body,
            self.backing,
            section_id,
            offset_index,
            value.as_bstr(),
        )
    }

    /// 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<Iter, Item>(&mut self, values: Iter) -> Result<(), crate::parse::span::Error>
    where
        Iter: IntoIterator<Item = Item>,
        Item: crate::AsBStr,
    {
        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,
                self.backing,
                *section_id,
                *offset_index,
                value.as_bstr(),
            )?;
        }
        Ok(())
    }

    /// Sets all values in this multivar to the provided one without owning the
    /// provided input.
    pub fn set_all(&mut self, input: impl crate::AsBStr) -> Result<(), crate::parse::span::Error> {
        let input = input.as_bstr();
        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,
                self.backing,
                *section_id,
                *offset_index,
                input,
            )?;
        }
        Ok(())
    }

    fn set_value_inner(
        value_name: &section::ValueName,
        offsets: &mut HashMap<SectionId, Vec<usize>>,
        section: &mut file::section::BodyData,
        backing: &mut Vec<u8>,
        section_id: SectionId,
        offset_index: usize,
        value: &BStr,
    ) -> Result<(), crate::parse::span::Error> {
        let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index);
        let whitespace = Whitespace::from_body(section, backing);
        let value = crate::parse::Span::append(backing, &escape_value(value))?;
        let key_sep_events = whitespace.key_value_separators(backing)?;
        let key = crate::parse::Span::append(backing, value_name.0.as_slice())?;

        let section = section.as_mut();
        section.drain(offset..offset + size);
        MultiValueMut::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len());
        section.insert(offset, Event::Value(value));
        section
            .splice(offset..offset, key_sep_events.into_iter().rev())
            .for_each(|_| {});
        section.insert(offset, Event::SectionValueName(key));
        Ok(())
    }

    /// 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: &HashMap<SectionId, Vec<usize>>,
        section_id: SectionId,
        offset_index: usize,
    ) -> (usize, usize) {
        offsets
            .get(&section_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(&section_id)
            .expect("known section id")
            .get_mut(offset_index)
            .unwrap()
            .deref_mut() = value;
    }
}