git-config 0.16.2

Please use `gix-<thiscrate>` instead ('git' -> 'gix')
Documentation
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<'b>(&mut self, key: Key<'event>, value: impl Into<&'b BStr>) -> Option<Cow<'event, BStr>> {
        match self.key_and_value_range_by(&key) {
            None => {
                self.push(key, Some(value.into()));
                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()).into()));
                Some(ret)
            }
        }
    }

    /// Removes the latest value by key and returns it, if it exists.
    pub fn remove(&mut self, key: impl AsRef<str>) -> Option<Cow<'event, BStr>> {
        let key = Key::from_str_unchecked(key.as_ref());
        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(|b| b.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.insert_many(index.0, sep_events.into_iter().rev());

        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 parse::section::Events<'event> {
        &mut self.0
    }
}