summaryrefslogtreecommitdiffstats
path: root/vendor/gix-config/src/file/mutable/section.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-config/src/file/mutable/section.rs')
-rw-r--r--vendor/gix-config/src/file/mutable/section.rs316
1 files changed, 316 insertions, 0 deletions
diff --git a/vendor/gix-config/src/file/mutable/section.rs b/vendor/gix-config/src/file/mutable/section.rs
new file mode 100644
index 000000000..def68ac60
--- /dev/null
+++ b/vendor/gix-config/src/file/mutable/section.rs
@@ -0,0 +1,316 @@
+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
+ }
+}