use std::borrow::Cow; use bstr::{BStr, BString, ByteSlice}; use super::is_git_whitespace; /// Removes quotes, if any, from the provided inputs, and transforms the /// escape sequences `\n`, `\t` and `\b` into newline, tab and backspace /// (byte `0x08`) respectively, matching how `git` interprets them. /// /// It assumes the input contains a even number of unescaped quotes, /// and will unescape escaped quotes and everything else (even though the latter /// would have been rejected in the parsing stage). /// /// The return values should be safe for value interpretation. /// If normalization requires no byte changes, including when it only trims enclosing quotes, /// the returned value borrows from `input`. Transforming escapes or embedded quotes produces an /// owned value instead. /// /// This is the function used to normalize raw values from higher level /// abstractions. Generally speaking these /// high level abstractions will handle normalization for you, and you do not /// need to call this yourself. However, if you're directly handling events /// from the parser, you may want to use this to help with value interpretation. /// /// # Examples /// /// Internally quoted values are turned into an owned variant with quotes removed. /// /// ``` /// # use gix_config::value::normalize; /// assert_eq!(&*normalize("hello \"world\""), "hello world"); /// ``` /// /// Escaped quotes are unescaped. /// /// ``` /// # use gix_config::value::normalize; /// assert_eq!(&*normalize(r#"hello "world\"""#), r#"hello world""#); /// ``` #[must_use] pub fn normalize(input: &(impl crate::AsBStr + ?Sized)) -> Cow<'_, BStr> { normalize_inner(input.as_bstr()) } fn normalize_inner(input: &BStr) -> Cow<'_, BStr> { // An optimization to strip enclosing quotes without producing a new value/copy it. if input.len() >= 2 && input[0] == b'"' && input[input.len() - 1] == b'"' && input[1..input.len() - 1].find_byteset(br#"\""#).is_none() { return Cow::Borrowed(input[1..input.len() - 1].as_ref()); } if input.find_byteset(br#"\""#).is_none() { return Cow::Borrowed(input); } let mut out: BString = Vec::with_capacity(input.len()).into(); let mut bytes = input.iter().copied(); let mut is_in_quotes = false; while let Some(c) = bytes.next() { match c { b'\\' => match bytes.next() { Some(b'n') => out.push(b'\n'), Some(b't') => out.push(b'\t'), Some(b'b') => out.push(b'\x08'), Some(c) => { out.push(c); } None => break, }, b'"' => is_in_quotes = !is_in_quotes, // Empty quotes contribute no bytes, so Git keeps ignoring unquoted whitespace after them. c if !is_in_quotes && out.is_empty() && is_git_whitespace(c) => {} _ => out.push(c), } } Cow::Owned(out) }