1573 lines · 47.3 KB · Rust
RawDownload
//! Rust containers for valid `git-config` types. use bstr::BStr;use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; use quick_error::quick_error;#[cfg(feature = "serde")]use serde::{Serialize, Serializer}; /// Removes quotes, if any, from the provided inputs. This assumes the input/// contains a even number of unescaped quotes, and will unescape escaped/// quotes. The return values should be safe for value interpretation.////// This has optimizations for fully-quoted values, where the returned value/// will be a borrowed reference if the only mutation necessary is to unquote/// the value.////// This is the function used to normalize raw values from higher level/// abstractions over the [`parser`] implementation. 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.////// Generally speaking, you'll want to use one of the variants of this function,/// such as [`normalize_str`] or [`normalize_vec`].////// # Examples////// Values don't need modification are returned borrowed, without allocation.////// ```/// # use std::borrow::Cow;/// # use git_config::values::normalize_str;/// assert_eq!(normalize_str("hello world"), Cow::Borrowed(b"hello world".as_slice()));/// ```////// Fully quoted values are optimized to not need allocations.////// ```/// # use std::borrow::Cow;/// # use git_config::values::normalize_str;/// assert_eq!(normalize_str("\"hello world\""), Cow::Borrowed(b"hello world".as_slice()));/// ```////// Quoted values are unwrapped as an owned variant.////// ```/// # use std::borrow::Cow;/// # use git_config::values::normalize_str;/// assert_eq!(normalize_str("hello \"world\""), Cow::<[u8]>::Owned(b"hello world".to_vec()));/// ```////// Escaped quotes are unescaped.////// ```/// # use std::borrow::Cow;/// # use git_config::values::normalize_str;/// assert_eq!(normalize_str(r#"hello "world\"""#), Cow::<[u8]>::Owned(br#"hello world""#.to_vec()));/// ```////// [`parser`]: crate::parser::Parser#[must_use]pub fn normalize_cow(input: Cow<'_, [u8]>) -> Cow<'_, [u8]> {    let size = input.len();    if &*input == b"\"\"" {        return Cow::Borrowed(&[]);    }     if size >= 3 && input[0] == b'=' && input[size - 1] == b'=' && input[size - 2] != b'\\' {        match input {            Cow::Borrowed(input) => return normalize_bytes(&input[1..size]),            Cow::Owned(mut input) => {                input.pop();                input.remove(0);                return normalize_vec(input);            }        }    }     let mut owned = vec![];     let mut first_index = 0;    let mut last_index = 0;    let mut was_escaped = false;    for (i, c) in input.iter().enumerate() {        if was_escaped {            was_escaped = false;            if *c == b'"' {                if first_index == 0 {                    owned.extend(&input[last_index..i - 1]);                    last_index = i;                } else {                    owned.extend(&input[first_index..i - 1]);                    first_index = i;                }            }            continue;        }         if *c == b'\\' {            was_escaped = true;        } else if *c == b'"' {            if first_index == 0 {                owned.extend(&input[last_index..i]);                first_index = i + 1;            } else {                owned.extend(&input[first_index..i]);                first_index = 0;                last_index = i + 1;            }        }    }     if last_index == 0 {        input    } else {        owned.extend(&input[last_index..]);        Cow::Owned(owned)    }} /// `&[u8]` variant of [`normalize_cow`].#[inline]#[must_use]pub fn normalize_bytes(input: &[u8]) -> Cow<'_, [u8]> {    normalize_cow(Cow::Borrowed(input))} /// `Vec[u8]` variant of [`normalize_cow`].#[inline]#[must_use]pub fn normalize_vec(input: Vec<u8>) -> Cow<'static, [u8]> {    normalize_cow(Cow::Owned(input))} /// [`str`] variant of [`normalize_cow`].#[inline]#[must_use]pub fn normalize_str(input: &str) -> Cow<'_, [u8]> {    normalize_bytes(input.as_bytes())} /// Converts string to byte slice#[cfg(test)]fn b(s: &str) -> &[u8] {    s.as_bytes()} // TODO: remove bytes/// Any string value#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]pub struct Bytes<'a> {    /// bytes    pub value: Cow<'a, [u8]>,} impl<'a> From<&'a [u8]> for Bytes<'a> {    #[inline]    fn from(s: &'a [u8]) -> Self {        Self {            value: Cow::Borrowed(s),        }    }} impl From<Vec<u8>> for Bytes<'_> {    fn from(s: Vec<u8>) -> Self {        Self { value: Cow::Owned(s) }    }} impl<'a> From<Cow<'a, [u8]>> for Bytes<'a> {    #[inline]    fn from(c: Cow<'a, [u8]>) -> Self {        match c {            Cow::Borrowed(c) => Self::from(c),            Cow::Owned(c) => Self::from(c),        }    }} /// Any string value#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]pub struct String<'a> {    /// The string value    pub value: Cow<'a, BStr>,} impl<'a> From<Cow<'a, [u8]>> for String<'a> {    #[inline]    fn from(c: Cow<'a, [u8]>) -> Self {        String {            value: match c {                Cow::Borrowed(c) => Cow::Borrowed(c.into()),                Cow::Owned(c) => Cow::Owned(c.into()),            },        }    }} ///pub mod path {    use std::borrow::Cow;     #[cfg(not(target_os = "windows"))]    use pwd::Passwd;    use quick_error::ResultExt;     use crate::values::Path;     pub mod interpolate {        use quick_error::quick_error;         quick_error! {            #[derive(Debug)]            /// The error returned by [`Path::interpolate()`].            #[allow(missing_docs)]            pub enum Error {                Missing { what: &'static str } {                    display("{} is missing", what)                }                Utf8Conversion(what: &'static str, err: git_features::path::Utf8Error) {                    display("Ill-formed UTF-8 in {}", what)                    context(what: &'static str, err: git_features::path::Utf8Error) -> (what, err)                    source(err)                }                UsernameConversion(err: std::str::Utf8Error) {                    display("Ill-formed UTF-8 in username")                    source(err)                    from()                }                PwdFileQuery {                    display("User home info missing")                }                UserInterpolationUnsupported {                    display("User interpolation is not available on this platform")                }            }        }    }     impl<'a> Path<'a> {        /// Interpolates this path into a file system path.        ///        /// If this path starts with `~/` or `~user/` or `%(prefix)/`        ///  - `~/` is expanded to the value of `$HOME` on unix based systems. On windows, `SHGetKnownFolderPath` is used.        /// See also [dirs](https://crates.io/crates/dirs).        ///  - `~user/` to the specified user’s home directory, e.g `~alice` might get expanded to `/home/alice` on linux.        /// The interpolation uses `getpwnam` sys call and is therefore not available on windows. See also [pwd](https://crates.io/crates/pwd).        ///  - `%(prefix)/` is expanded to the location where gitoxide is installed. This location is not known at compile time and therefore need to be        /// optionally provided by the caller through `git_install_dir`.        ///        /// Any other, non-empty path value is returned unchanged and error is returned in case of an empty path value.        pub fn interpolate(            self,            git_install_dir: Option<&std::path::Path>,        ) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {            if self.is_empty() {                return Err(interpolate::Error::Missing { what: "path" });            }             const PREFIX: &[u8] = b"%(prefix)/";            const USER_HOME: &[u8] = b"~/";            if self.starts_with(PREFIX) {                let git_install_dir = git_install_dir.ok_or(interpolate::Error::Missing {                    what: "git install dir",                })?;                let (_prefix, path_without_trailing_slash) = self.split_at(PREFIX.len());                let path_without_trailing_slash =                    git_features::path::from_byte_vec(path_without_trailing_slash).context("path past %(prefix)")?;                Ok(git_install_dir.join(path_without_trailing_slash).into())            } else if self.starts_with(USER_HOME) {                let home_path = dirs::home_dir().ok_or(interpolate::Error::Missing { what: "home dir" })?;                let (_prefix, val) = self.split_at(USER_HOME.len());                let val = git_features::path::from_bytes(val).context("path past ~/")?;                Ok(home_path.join(val).into())            } else if self.starts_with(b"~") && self.contains(&b'/') {                self.interpolate_user()            } else {                Ok(git_features::path::from_bytes(self.value).context("unexpanded path")?)            }        }         #[cfg(target_os = "windows")]        fn interpolate_user(self) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {            Err(interpolate::Error::UserInterpolationUnsupported)        }         #[cfg(not(target_os = "windows"))]        fn interpolate_user(self) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {            let (_prefix, val) = self.split_at("/".len());            let i = val                .iter()                .position(|&e| e == b'/')                .ok_or(interpolate::Error::Missing { what: "/" })?;            let (username, path_with_leading_slash) = val.split_at(i);            let username = std::str::from_utf8(username)?;            let home = Passwd::from_name(username)                .map_err(|_| interpolate::Error::PwdFileQuery)?                .ok_or(interpolate::Error::Missing { what: "pwd user info" })?                .dir;            let path_past_user_prefix = git_features::path::from_byte_slice(&path_with_leading_slash["/".len()..])                .context("path past ~user/")?;            Ok(std::path::PathBuf::from(home).join(path_past_user_prefix).into())        }    }     #[cfg(test)]    mod interpolate_tests {        use std::borrow::Cow;         use crate::values::{b, path::interpolate::Error, Path};         #[test]        fn no_interpolation_for_paths_without_tilde_or_prefix() {            let path = &b"/foo/bar"[..];            let actual = Path::from(Cow::Borrowed(path));            assert_eq!(&*actual, path);            assert!(                matches!(&actual.value, Cow::Borrowed(_)),                "it does not unnecessarily copy values"            );        }         #[test]        fn empty_path_is_error() {            assert!(matches!(                Path::from(Cow::Borrowed(b(""))).interpolate(None),                Err(Error::Missing { what: "path" })            ));        }         #[test]        fn prefix_substitutes_git_install_dir() {            for git_install_dir in &["/tmp/git", "C:\\git"] {                for (val, expected) in &[                    (&b"%(prefix)/foo/bar"[..], "foo/bar"),                    (b"%(prefix)/foo\\bar", "foo\\bar"),                ] {                    let expected = &std::path::PathBuf::from(format!(                        "{}{}{}",                        git_install_dir,                        std::path::MAIN_SEPARATOR,                        expected                    ));                    assert_eq!(                        &*Path::from(Cow::Borrowed(*val))                            .interpolate(Some(std::path::Path::new(git_install_dir)))                            .unwrap(),                        expected,                        "prefix interpolation keeps separators as they are"                    );                }            }        }         #[test]        fn prefix_substitution_skipped_with_dot_slash() {            let path = "./%(prefix)/foo/bar";            let git_install_dir = "/tmp/git";            assert_eq!(                Path::from(Cow::Borrowed(b(path)))                    .interpolate(Some(std::path::Path::new(git_install_dir)))                    .unwrap(),                std::path::Path::new(path)            );        }         #[test]        fn tilde_substitutes_current_user() {            let path = &b"~/foo/bar"[..];            let expected = format!(                "{}{}foo/bar",                dirs::home_dir().expect("empty home").display(),                std::path::MAIN_SEPARATOR            );            assert_eq!(                Path::from(Cow::Borrowed(path)).interpolate(None).unwrap().as_ref(),                std::path::Path::new(&expected),                "note that path separators are not turned into slashes as we work with `std::path::Path`"            );        }         #[cfg(target_os = "windows")]        #[test]        fn tilde_with_given_user_is_unsupported_on_windows() {            assert!(matches!(                Path::from(Cow::Borrowed(&b"~baz/foo/bar"[..])).interpolate(None),                Err(Error::UserInterpolationUnsupported)            ));        }         #[cfg(not(target_os = "windows"))]        #[test]        fn tilde_with_given_user() {            let user = std::env::var("USER").unwrap();            let home = std::env::var("HOME").unwrap();            let specific_user_home = format!("~{}", user);             for path_suffix in &["foo/bar", "foo\\bar", ""] {                let path = format!("{}{}{}", specific_user_home, std::path::MAIN_SEPARATOR, path_suffix);                let expected = format!("{}{}{}", home, std::path::MAIN_SEPARATOR, path_suffix);                assert_eq!(                    Path::from(Cow::Borrowed(b(&path))).interpolate(None).unwrap(),                    std::path::Path::new(&expected),                    "it keeps path separators as is"                );            }        }    }} /// Any value that can be interpreted as a file path.////// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences.#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]pub struct Path<'a> {    /// The path string, un-interpolated    pub value: Cow<'a, [u8]>,} impl<'a> std::ops::Deref for Path<'a> {    type Target = [u8];     fn deref(&self) -> &Self::Target {        self.value.as_ref()    }} impl<'a> AsRef<[u8]> for Path<'a> {    fn as_ref(&self) -> &[u8] {        self.value.as_ref()    }} impl<'a> From<Cow<'a, [u8]>> for Path<'a> {    #[inline]    fn from(value: Cow<'a, [u8]>) -> Self {        Path { value }    }} /// Any value that can be interpreted as a boolean.////// Note that while values can effectively be any byte string, the `git-config`/// documentation has a strict subset of values that may be interpreted as a/// boolean value, all of which are ASCII and thus UTF-8 representable./// Consequently, variants hold [`str`]s rather than [`[u8]`]s.#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]#[allow(missing_docs)]pub enum Boolean<'a> {    True(TrueVariant<'a>),    False(Cow<'a, str>),} impl Boolean<'_> {    /// Return ourselves as plain bool.    pub fn to_bool(&self) -> bool {        match self {            Boolean::True(_) => true,            Boolean::False(_) => false,        }    }    /// Generates a byte representation of the value. This should be used when    /// non-UTF-8 sequences are present or a UTF-8 representation can't be    /// guaranteed.    #[inline]    #[must_use]    pub fn to_vec(&self) -> Vec<u8> {        self.into()    }     /// Generates a byte representation of the value. This should be used when    /// non-UTF-8 sequences are present or a UTF-8 representation can't be    /// guaranteed.    #[inline]    #[must_use]    pub fn as_bytes(&self) -> &[u8] {        self.into()    }} quick_error! {    #[derive(Debug, PartialEq)]    /// The error returned when creating `Boolean` from byte string.    #[allow(missing_docs)]    pub enum BooleanError {        InvalidFormat {            display("Invalid argument format")        }    }} impl<'a> TryFrom<&'a [u8]> for Boolean<'a> {    type Error = BooleanError;     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {        if let Ok(v) = TrueVariant::try_from(value) {            return Ok(Self::True(v));        }

Lines 1–500 of 1573.