483 lines · 17.4 KB · Rust
RawDownload
use std::{collections::HashMap, ops::Range}; use bstr::{BStr, BString, ByteSlice, ByteVec};use gix_sec::Trust;use smallvec::SmallVec; use crate::{    AsBStrOpt,    file::{        self, Index, IntoBStringOpt, SectionData, SectionId, SectionLookup, SectionRef, Size,        mutable::{Whitespace, escape_value},    },    lookup,    parse::{self, Event, Span, section::ValueName},    value::normalize,}; /// A opaque type that represents a mutable reference to a section.#[derive(Debug)]pub struct SectionMut<'a> {    section: &'a mut SectionData,    backing: &'a mut Vec<u8>,    /// Present only if backed by [`crate::File`]    lookup: Option<LookupMut<'a>>,    implicit_newline: bool,    whitespace: Whitespace,    newline: SmallVec<[u8; 2]>,} #[derive(Debug)]pub(crate) struct LookupMut<'a> {    pub(crate) tree: &'a mut HashMap<parse::section::Name, SectionLookup>,    pub(crate) order: &'a [SectionId],} /// Mutating methods.impl SectionMut<'_> {    /// Rename this section to `name` and the optional `subsection_name`.    ///    /// Section names are not unique, and renaming into an existing section name is permitted.    pub fn rename(        &mut self,        name: impl AsRef<str>,        subsection_name: impl IntoBStringOpt,    ) -> Result<&mut Self, parse::section::header::Error> {        let header = parse::section::HeaderData::new_in(name, subsection_name.into_bstring_opt(), self.backing)?;        self.set_header(header);        Ok(self)    }     /// Adds an entry to the end of this section name `value_name` 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(        &mut self,        value_name: impl AsRef<str>,        value: impl AsBStrOpt,    ) -> Result<&mut Self, file::section::value::Error> {        let value_name = ValueName::try_from(value_name.as_ref())?;        self.push_with_comment_inner(value_name, value.as_bstr_opt(), None)?;        Ok(self)    }     /// Adds an entry to the end of this section name `value_name` 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(        &mut self,        value_name: impl AsRef<str>,        value: impl AsBStrOpt,        comment: impl crate::AsBStr,    ) -> Result<&mut Self, file::section::value::Error> {        let value_name = ValueName::try_from(value_name.as_ref())?;        self.push_with_comment_inner(value_name, value.as_bstr_opt(), Some(comment.as_bstr()))?;        Ok(self)    }     fn push_with_comment_inner(        &mut self,        value_name: ValueName,        value: Option<&BStr>,        comment: Option<&BStr>,    ) -> Result<(), parse::span::Error> {        let mut events = Vec::new();        if let Some(ws) = &self.whitespace.pre_key {            events.push(Event::Whitespace(Span::append(self.backing, ws)?));        }         events.push(Event::SectionValueName(Span::append(            self.backing,            value_name.0.as_slice(),        )?));        match value {            Some(value) => {                events.extend(self.whitespace.key_value_separators(self.backing)?);                events.push(Event::Value(Span::append(self.backing, &escape_value(value))?));            }            None => events.push(Event::Value(Span::append(self.backing, b"")?)),        }        if let Some(comment) = comment {            events.push(Event::Whitespace(Span::append(self.backing, b" ")?));            let mut c = Vec::with_capacity(comment.len());            let mut bytes = comment.iter().peekable();            if !bytes.peek().is_none_or(|b| b.is_ascii_whitespace()) {                c.insert(0, b' ');            }            c.extend(bytes.map(|b| if *b == b'\n' { b' ' } else { *b }));            events.push(Event::Comment(parse::Comment {                tag: b'#',                text: Span::append(self.backing, &c)?,            }));        }        if self.implicit_newline {            events.push(Event::Newline(Span::append(self.backing, &self.newline)?));        }        self.section.body.0.extend(events);        Ok(())    }     /// 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<(String, BString)> {        let mut values: Vec<BString> = Vec::new();        // events are popped in reverse order        let body = &mut self.section.body.0;        while let Some(e) = body.pop() {            match e {                Event::SectionValueName(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.as_bstr_in(self.backing)                                .to_str()                                .expect("parsed value names are ASCII")                                .to_owned(),                            normalize(&value).into_owned(),                        ));                    }                     return Some((                        k.as_bstr_in(self.backing)                            .to_str()                            .expect("parsed value names are ASCII")                            .to_owned(),                        normalize(&{                            let mut s = BString::default();                            for value in values.into_iter().rev() {                                s.push_str(value.as_slice());                            }                            s                        })                        .into_owned(),                    ));                }                Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) => {                    values.push(v.to_bstring_in(self.backing));                }                _ => (),            }        }        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,        value_name: impl AsRef<str>,        value: impl crate::AsBStr,    ) -> Result<Option<BString>, file::section::value::Error> {        let value_name = ValueName::try_from(value_name.as_ref())?;        self.set_inner(value_name, value.as_bstr()).map_err(Into::into)    }     pub(crate) fn set_inner(        &mut self,        value_name: ValueName,        value: &BStr,    ) -> Result<Option<BString>, parse::span::Error> {        match self.section.body.key_and_value_range_by_in(self.backing, &value_name) {            None => {                self.push_with_comment_inner(value_name, Some(value), None)?;                Ok(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 value = Span::append(self.backing, &escape_value(value))?;                let ret = self.remove_internal(value_range, false);                self.section.body.0.insert(range_start, Event::Value(value));                Ok(Some(ret))            }        }    }     /// Set the trust level in the meta-data of this section to `trust`.    pub fn set_trust(&mut self, trust: Trust) -> &mut Self {        let mut meta = (*self.section.meta).clone();        meta.trust = trust;        self.section.meta = meta.into();        self    }     /// Removes the latest value by key and returns it, if it exists.    pub fn remove(&mut self, value_name: &str) -> Option<BString> {        let key = ValueName::from_str_unchecked(value_name);        let (key_range, _value_range) = self.section.body.key_and_value_range_by_in(self.backing, &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) -> Result<&mut Self, parse::span::Error> {        let newline = Span::append(self.backing, &self.newline)?;        self.section.body.0.push(Event::Newline(newline));        Ok(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.    // TODO(error): make it fallible    pub fn set_leading_whitespace(&mut self, whitespace: impl IntoBStringOpt) -> &mut Self {        let whitespace = whitespace.into_bstring_opt();        assert!(            whitespace                .as_deref()                .is_none_or(|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_ref().map(|v| v.as_slice().as_bstr())    }     /// Return the immutable section view backing this mutable handle.    pub fn section(&self) -> SectionRef<'_> {        SectionRef::from_data(self.section, self.backing)    }     /// Return the header of the section backing this mutable handle.    pub fn header(&self) -> file::section::HeaderRef<'_> {        file::section::HeaderRef {            header: &self.section.header,            backing: self.backing,        }    }     /// Return the unique `id` of the section backing this mutable handle.    pub fn id(&self) -> file::SectionId {        self.section.id    }     /// Return the section body backing this mutable handle.    pub fn body(&self) -> file::section::BodyRef<'_> {        file::section::BodyRef {            body: &self.section.body,            backing: self.backing,        }    }     /// Serialize the section backing this mutable handle into a `BString`.    #[must_use]    pub fn to_bstring(&self) -> BString {        self.section().to_bstring()    }     /// Return additional information about this section's origin.    pub fn meta(&self) -> &file::Metadata {        &self.section.meta    }     /// Retrieves the last matching value in this section with the given value name, if present.    #[must_use]    pub fn value(&self, value_name: impl AsRef<str>) -> Option<BString> {        self.section            .body            .value_implicit_in(self.backing, value_name.as_ref())            .flatten()    }     /// Retrieves the last matching value in this section, including implicit values.    #[must_use]    pub fn value_implicit(&self, value_name: &str) -> Option<Option<BString>> {        self.section.body.value_implicit_in(self.backing, value_name)    }     /// Retrieves all values that have the provided value name.    #[must_use]    pub fn values(&self, value_name: &str) -> Vec<BString> {        self.section.body.values_in(self.backing, value_name)    }     /// Returns an iterator visiting all value names in order.    pub fn value_names(&self) -> impl Iterator<Item = String> + '_ {        self.section.body.as_ref().iter().filter_map(move |e| match e {            Event::SectionValueName(k) => Some(                k.as_bstr_in(self.backing)                    .to_str()                    .expect("parsed value names are ASCII")                    .to_owned(),            ),            _ => None,        })    }     /// Returns true if the section contains the provided value name.    #[must_use]    pub fn contains_value_name(&self, value_name: &str) -> bool {        self.section.body.contains_value_name_in(self.backing, value_name)    }     /// Returns the number of values in the section.    #[must_use]    pub fn num_values(&self) -> usize {        self.section.body.num_values()    }     /// Returns if the section is empty.    #[must_use]    pub fn is_void(&self) -> bool {        self.section.body.is_void()    }     /// 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_ref().map(|v| v.as_slice().as_bstr()),            self.whitespace.post_sep.as_ref().map(|v| v.as_slice().as_bstr()),        )    }} // Internal methods that may require exact indices for faster operations.impl<'a> SectionMut<'a> {    pub(crate) fn new(        section: &'a mut SectionData,        backing: &'a mut Vec<u8>,        lookup: Option<LookupMut<'a>>,        newline: SmallVec<[u8; 2]>,    ) -> Self {        let whitespace = Whitespace::from_body(&section.body, backing);        Self {            section,            backing,            lookup,            implicit_newline: true,            whitespace,            newline,        }    }     pub(crate) fn set_header(&mut self, header: parse::section::HeaderData) {        if let Some(lookup) = &mut self.lookup {            crate::file::util::set_section_header(self.section, self.backing, lookup.tree, lookup.order, header);        } else {            self.section.header = header;        }    }     pub(crate) fn get(&self, key: &ValueName, start: Index, end: Index) -> Result<BString, lookup::existing::Error> {        let mut expect_value = false;        let mut concatenated_value = BString::default();         for event in &self.section.body.0[start.0..end.0] {            match event {                Event::SectionValueName(event_key)                    if event_key                        .as_bstr_in(self.backing)                        .eq_ignore_ascii_case(key.0.as_slice()) =>                {                    expect_value = true;                }                Event::Value(v) if expect_value => return Ok(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 => {                    concatenated_value.push_str(v.as_slice_in(self.backing));                    return Ok(normalize(&concatenated_value).into_owned());                }                _ => (),            }        }         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: ValueName,        value: &BStr,    ) -> Result<Size, parse::span::Error> {        let mut size = 0;        let value = Span::append(self.backing, &escape_value(value))?;        let sep_events = self.whitespace.key_value_separators(self.backing)?;        let key = Span::append(self.backing, key.0.as_slice())?;         let body = &mut self.section.body.0;        body.insert(index.0, Event::Value(value));        size += 1;         size += sep_events.len();        body.splice(index.0..index.0, sep_events.into_iter().rev())            .for_each(|_| {});         body.insert(index.0, Event::SectionValueName(key));        size += 1;         Ok(Size(size))    }     /// Performs the removal, assuming the range is valid.    fn remove_internal(&mut self, range: Range<usize>, fix_whitespace: bool) -> BString {        let events = &mut self.section.body.0;        if fix_whitespace && events.get(range.end).is_some_and(|ev| matches!(ev, Event::Newline(_))) {            events.remove(range.end);        }        let value = events.drain(range.clone()).fold(BString::default(), |mut acc, e| {            if let Event::Value(v) | Event::ValueNotDone(v) | Event::ValueDone(v) = e {                acc.push_str(v.as_slice_in(self.backing));            }            acc        });        if fix_whitespace            && range                .start                .checked_sub(1)                .and_then(|pos| events.get(pos))                .is_some_and(|ev| matches!(ev, Event::Whitespace(_)))        {            events.remove(range.start - 1);        }        value    }} impl file::section::BodyData {    pub(crate) fn as_mut(&mut self) -> &mut Vec<Event> {        &mut self.0    }}