diff --git a/src/file.rs b/src/file.rs index 1c27305..96283cf 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,6 +1,6 @@ use crate::parser::{parse_from_bytes, Error, Event, ParsedSectionHeader, Parser}; use std::collections::{HashMap, VecDeque}; -use std::convert::TryFrom; +use std::{borrow::Borrow, convert::TryFrom}; use std::{borrow::Cow, fmt::Display}; #[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] @@ -86,7 +86,7 @@ enum LookupTreeNode<'a> { /// # use std::borrow::Cow; /// # use std::convert::TryFrom; /// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); -/// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(&Cow::Borrowed("d".as_bytes()))); +/// assert_eq!(git_config.get_raw_value("core", None, "a"), Ok(Cow::Borrowed("d".as_bytes()))); /// ``` /// /// Consider the `multi` variants of the methods instead, if you want to work @@ -198,8 +198,8 @@ impl<'a> GitConfig<'a> { /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom - pub fn get_value<'b, 'c, T: TryFrom<&'c [u8]>>( - &'c self, + pub fn get_value<'b, T: TryFrom>>( + &'a self, section_name: &'b str, subsection_name: Option<&'b str>, key: &'b str, @@ -232,34 +232,46 @@ impl<'a> GitConfig<'a> { /// This function will return an error if the key is not in the requested /// section and subsection, or if the section and subsection do not exist. pub fn get_raw_value<'b>( - &self, + &'a self, section_name: &'b str, subsection_name: Option<&'b str>, key: &'b str, - ) -> Result<&Cow<'a, [u8]>, GitConfigError<'b>> { + ) -> Result, GitConfigError<'b>> { let key = key; // Note: cannot wrap around the raw_multi_value method because we need // to guarantee that the highest section id is used (so that we follow // the "last one wins" resolution strategy by `git-config`). let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?; - // section_id is guaranteed to exist in self.sections, else we have a - // violated invariant. - let events = self.sections.get(§ion_id).unwrap(); let mut found_key = false; let mut latest_value = None; - for event in events { + let mut partial_value = None; + + // section_id is guaranteed to exist in self.sections, else we have a + // violated invariant. + for event in self.sections.get(§ion_id).unwrap() { match event { Event::Key(event_key) if *event_key == key => found_key = true, Event::Value(v) if found_key => { found_key = false; - latest_value = Some(v); + latest_value = Some(Cow::Borrowed(v.borrow())); + partial_value = None; + } + Event::ValueNotDone(v) if found_key => { + latest_value = None; + partial_value = Some((*v).to_vec()); + } + Event::ValueDone(v) if found_key => { + found_key = false; + partial_value.as_mut().unwrap().extend(&**v); } _ => (), } } - latest_value.ok_or(GitConfigError::KeyDoesNotExist(key)) + latest_value + .or_else(|| partial_value.map(Cow::Owned)) + .ok_or(GitConfigError::KeyDoesNotExist(key)) } /// Returns a mutable reference to an uninterpreted value given a section, @@ -328,9 +340,9 @@ impl<'a> GitConfig<'a> { /// assert_eq!( /// git_config.get_raw_multi_value("core", None, "a"), /// Ok(vec![ - /// &Cow::<[u8]>::Borrowed(b"b"), - /// &Cow::<[u8]>::Borrowed(b"c"), - /// &Cow::<[u8]>::Borrowed(b"d"), + /// Cow::<[u8]>::Borrowed(b"b"), + /// Cow::<[u8]>::Borrowed(b"c"), + /// Cow::<[u8]>::Borrowed(b"d"), /// ]), /// ); /// ``` @@ -348,7 +360,7 @@ impl<'a> GitConfig<'a> { section_name: &'b str, subsection_name: Option<&'b str>, key: &'b str, - ) -> Result>, GitConfigError<'b>> { + ) -> Result>, GitConfigError<'b>> { let key = key; let mut values = vec![]; for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? { @@ -359,7 +371,7 @@ impl<'a> GitConfig<'a> { match event { Event::Key(event_key) if *event_key == key => found_key = true, Event::Value(v) if found_key => { - values.push(v); + values.push(Cow::Borrowed(v.borrow())); found_key = false; } _ => (), @@ -399,9 +411,9 @@ impl<'a> GitConfig<'a> { /// assert_eq!( /// git_config.get_raw_multi_value("core", None, "a")?, /// vec![ - /// &Cow::Borrowed(b"b"), - /// &Cow::Borrowed(b"c"), - /// &Cow::Borrowed(b"d") + /// Cow::Borrowed(b"b"), + /// Cow::Borrowed(b"c"), + /// Cow::Borrowed(b"d") /// ] /// ); /// for value in git_config.get_raw_multi_value_mut("core", None, "a")? { @@ -410,9 +422,9 @@ impl<'a> GitConfig<'a> { /// assert_eq!( /// git_config.get_raw_multi_value("core", None, "a")?, /// vec![ - /// &Cow::Borrowed(b"g"), - /// &Cow::Borrowed(b"g"), - /// &Cow::Borrowed(b"g") + /// Cow::Borrowed(b"g"), + /// Cow::Borrowed(b"g"), + /// Cow::Borrowed(b"g") /// ], /// ); /// # Ok::<(), GitConfigError>(()) @@ -526,7 +538,7 @@ impl<'a> GitConfig<'a> { /// # use std::convert::TryFrom; /// # let mut git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// git_config.set_raw_value("core", None, "a", "e".as_bytes())?; - /// assert_eq!(git_config.get_raw_value("core", None, "a")?, &Cow::Borrowed(b"e")); + /// assert_eq!(git_config.get_raw_value("core", None, "a")?, Cow::Borrowed(b"e")); /// # Ok::<(), GitConfigError>(()) /// ``` /// @@ -585,9 +597,9 @@ impl<'a> GitConfig<'a> { /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.get_raw_multi_value("core", None, "a")?; - /// assert!(fetched_config.contains(&&Cow::Borrowed(b"x"))); - /// assert!(fetched_config.contains(&&Cow::Borrowed(b"y"))); - /// assert!(fetched_config.contains(&&Cow::Borrowed(b"z"))); + /// assert!(fetched_config.contains(&Cow::Borrowed(b"x"))); + /// assert!(fetched_config.contains(&Cow::Borrowed(b"y"))); + /// assert!(fetched_config.contains(&Cow::Borrowed(b"z"))); /// # Ok::<(), GitConfigError>(()) /// ``` /// @@ -604,8 +616,8 @@ impl<'a> GitConfig<'a> { /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; /// let fetched_config = git_config.get_raw_multi_value("core", None, "a")?; - /// assert!(fetched_config.contains(&&Cow::Borrowed(b"x"))); - /// assert!(fetched_config.contains(&&Cow::Borrowed(b"y"))); + /// assert!(fetched_config.contains(&Cow::Borrowed(b"x"))); + /// assert!(fetched_config.contains(&Cow::Borrowed(b"y"))); /// # Ok::<(), GitConfigError>(()) /// ``` /// @@ -623,7 +635,7 @@ impl<'a> GitConfig<'a> { /// Cow::Borrowed(b"discarded"), /// ]; /// git_config.set_raw_multi_value("core", None, "a", new_values.into_iter())?; - /// assert!(!git_config.get_raw_multi_value("core", None, "a")?.contains(&&Cow::Borrowed(b"discarded"))); + /// assert!(!git_config.get_raw_multi_value("core", None, "a")?.contains(&Cow::Borrowed(b"discarded"))); /// # Ok::<(), GitConfigError>(()) /// ``` /// @@ -968,11 +980,11 @@ mod get_raw_value { let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::<[u8]>::Borrowed(b"b")) + Ok(Cow::<[u8]>::Borrowed(b"b")) ); assert_eq!( config.get_raw_value("core", None, "c"), - Ok(&Cow::<[u8]>::Borrowed(b"d")) + Ok(Cow::<[u8]>::Borrowed(b"d")) ); } @@ -981,7 +993,7 @@ mod get_raw_value { let config = GitConfig::try_from("[core]\na=b\na=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::<[u8]>::Borrowed(b"d")) + Ok(Cow::<[u8]>::Borrowed(b"d")) ); } @@ -990,7 +1002,7 @@ mod get_raw_value { let config = GitConfig::try_from("[core]\na=b\n[core]\na=d").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::<[u8]>::Borrowed(b"d")) + Ok(Cow::<[u8]>::Borrowed(b"d")) ); } @@ -1026,11 +1038,11 @@ mod get_raw_value { let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap(); assert_eq!( config.get_raw_value("core", None, "a"), - Ok(&Cow::<[u8]>::Borrowed(b"b")) + Ok(Cow::<[u8]>::Borrowed(b"b")) ); assert_eq!( config.get_raw_value("core", Some("a"), "a"), - Ok(&Cow::<[u8]>::Borrowed(b"c")) + Ok(Cow::<[u8]>::Borrowed(b"c")) ); } } @@ -1072,7 +1084,7 @@ mod get_raw_multi_value { let config = GitConfig::try_from("[core]\na=b\na=c").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), - vec![&Cow::Borrowed(b"b"), &Cow::Borrowed(b"c")] + vec![Cow::Borrowed(b"b"), Cow::Borrowed(b"c")] ); } @@ -1082,9 +1094,9 @@ mod get_raw_multi_value { assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), vec![ - &Cow::Borrowed(b"b"), - &Cow::Borrowed(b"c"), - &Cow::Borrowed(b"d") + Cow::Borrowed(b"b"), + Cow::Borrowed(b"c"), + Cow::Borrowed(b"d") ] ); } @@ -1121,11 +1133,11 @@ mod get_raw_multi_value { let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap(); assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), - vec![&Cow::Borrowed(b"b")] + vec![Cow::Borrowed(b"b")] ); assert_eq!( config.get_raw_multi_value("core", Some("a"), "a").unwrap(), - vec![&Cow::Borrowed(b"c")] + vec![Cow::Borrowed(b"c")] ); } @@ -1135,9 +1147,9 @@ mod get_raw_multi_value { assert_eq!( config.get_raw_multi_value("core", None, "a").unwrap(), vec![ - &Cow::Borrowed(b"b"), - &Cow::Borrowed(b"c"), - &Cow::Borrowed(b"d") + Cow::Borrowed(b"b"), + Cow::Borrowed(b"c"), + Cow::Borrowed(b"d") ] ); } diff --git a/src/values.rs b/src/values.rs index 2f8d0e4..beebbab 100644 --- a/src/values.rs +++ b/src/values.rs @@ -152,6 +152,35 @@ impl<'a> From<&'a [u8]> for Value<'a> { } } +impl From for Value<'_> { + fn from(s: String) -> Self { + Self::from(s.into_bytes()) + } +} + +impl From> for Value<'_> { + fn from(s: Vec) -> Self { + if let Ok(int) = Integer::try_from(s.as_ref()) { + return Self::Integer(int); + } + + if let Ok(color) = Color::try_from(s.as_ref()) { + return Self::Color(color); + } + + Boolean::try_from(s).map_or_else(|v| Self::Other(Cow::Owned(v)), Self::Boolean) + } +} + +impl<'a> From> for Value<'a> { + fn from(c: Cow<'a, [u8]>) -> Self { + match c { + Cow::Borrowed(c) => Self::from(c), + Cow::Owned(c) => Self::from(c), + } + } +} + // todo display for value #[cfg(feature = "serde")] @@ -175,11 +204,11 @@ impl Serialize for Value<'_> { /// 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(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] pub enum Boolean<'a> { True(TrueVariant<'a>), - False(&'a str), + False(Cow<'a, str>), } impl<'a> TryFrom<&'a str> for Boolean<'a> { @@ -204,13 +233,48 @@ impl<'a> TryFrom<&'a [u8]> for Boolean<'a> { || value.eq_ignore_ascii_case(b"zero") || value == b"\"\"" { - return Ok(Self::False(std::str::from_utf8(value).unwrap())); + return Ok(Self::False(std::str::from_utf8(value).unwrap().into())); } Err(()) } } +impl TryFrom for Boolean<'_> { + type Error = String; + + fn try_from(value: String) -> Result { + Self::try_from(value.into_bytes()).map_err(|v| String::from_utf8(v).unwrap()) + } +} + +impl TryFrom> for Boolean<'_> { + type Error = Vec; + + fn try_from(value: Vec) -> Result { + if value.eq_ignore_ascii_case(b"no") + || value.eq_ignore_ascii_case(b"off") + || value.eq_ignore_ascii_case(b"false") + || value.eq_ignore_ascii_case(b"zero") + || value == b"\"\"" + { + return Ok(Self::False(Cow::Owned(String::from_utf8(value).unwrap()))); + } + + TrueVariant::try_from(value).map(Self::True) + } +} + +impl<'a> TryFrom> for Boolean<'a> { + type Error = (); + fn try_from(c: Cow<'a, [u8]>) -> Result { + match c { + Cow::Borrowed(c) => Self::try_from(c), + Cow::Owned(c) => Self::try_from(c).map_err(|_| ()), + } + } +} + impl Display for Boolean<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -245,10 +309,10 @@ impl Serialize for Boolean<'_> { /// Discriminating enum between implicit and explicit truthy values. /// /// This enum is part of the [`Boolean`] struct. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] pub enum TrueVariant<'a> { - Explicit(&'a str), + Explicit(Cow<'a, str>), /// For values defined without a `= `. Implicit, } @@ -270,7 +334,7 @@ impl<'a> TryFrom<&'a [u8]> for TrueVariant<'a> { || value.eq_ignore_ascii_case(b"true") || value.eq_ignore_ascii_case(b"one") { - Ok(Self::Explicit(std::str::from_utf8(value).unwrap())) + Ok(Self::Explicit(std::str::from_utf8(value).unwrap().into())) } else if value.is_empty() { Ok(Self::Implicit) } else { @@ -279,6 +343,34 @@ impl<'a> TryFrom<&'a [u8]> for TrueVariant<'a> { } } +impl TryFrom for TrueVariant<'_> { + type Error = String; + + fn try_from(value: String) -> Result { + Self::try_from(value.into_bytes()).map_err(|v| String::from_utf8(v).unwrap()) + } +} + +impl TryFrom> for TrueVariant<'_> { + type Error = Vec; + + fn try_from(value: Vec) -> Result { + if value.eq_ignore_ascii_case(b"yes") + || value.eq_ignore_ascii_case(b"on") + || value.eq_ignore_ascii_case(b"true") + || value.eq_ignore_ascii_case(b"one") + { + Ok(Self::Explicit(Cow::Owned( + String::from_utf8(value).unwrap(), + ))) + } else if value.is_empty() { + Ok(Self::Implicit) + } else { + Err(value) + } + } +} + impl Display for TrueVariant<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Self::Explicit(v) = self { @@ -378,6 +470,24 @@ impl TryFrom<&[u8]> for Integer { } } +impl TryFrom> for Integer { + type Error = (); + + fn try_from(value: Vec) -> Result { + Self::try_from(value.as_ref()) + } +} + +impl TryFrom> for Integer { + type Error = (); + fn try_from(c: Cow<'_, [u8]>) -> Result { + match c { + Cow::Borrowed(c) => Self::try_from(c), + Cow::Owned(c) => Self::try_from(c).map_err(|_| ()), + } + } +} + /// Integer prefixes that are supported by `git-config`. /// /// These values are base-2 unit of measurements, not the base-10 variants. @@ -445,6 +555,14 @@ impl TryFrom<&[u8]> for IntegerSuffix { } } +impl TryFrom> for IntegerSuffix { + type Error = (); + + fn try_from(value: Vec) -> Result { + Self::try_from(value.as_ref()) + } +} + /// Any value that may contain a foreground color, background color, a /// collection of color (text) modifiers, or a combination of any of the /// aforementioned values. @@ -565,6 +683,24 @@ impl TryFrom<&[u8]> for Color { } } +impl TryFrom> for Color { + type Error = (); + + fn try_from(value: Vec) -> Result { + Self::try_from(value.as_ref()) + } +} + +impl TryFrom> for Color { + type Error = (); + fn try_from(c: Cow<'_, [u8]>) -> Result { + match c { + Cow::Borrowed(c) => Self::try_from(c), + Cow::Owned(c) => Self::try_from(c).map_err(|_| ()), + } + } +} + /// Discriminating enum for [`Color`] values. /// /// `git-config` supports the eight standard colors, their bright variants, an @@ -870,30 +1006,33 @@ mod boolean { #[test] fn from_str_false() { - assert_eq!(Boolean::try_from("no"), Ok(Boolean::False("no"))); - assert_eq!(Boolean::try_from("off"), Ok(Boolean::False("off"))); - assert_eq!(Boolean::try_from("false"), Ok(Boolean::False("false"))); - assert_eq!(Boolean::try_from("zero"), Ok(Boolean::False("zero"))); - assert_eq!(Boolean::try_from("\"\""), Ok(Boolean::False("\"\""))); + assert_eq!(Boolean::try_from("no"), Ok(Boolean::False("no".into()))); + assert_eq!(Boolean::try_from("off"), Ok(Boolean::False("off".into()))); + assert_eq!( + Boolean::try_from("false"), + Ok(Boolean::False("false".into())) + ); + assert_eq!(Boolean::try_from("zero"), Ok(Boolean::False("zero".into()))); + assert_eq!(Boolean::try_from("\"\""), Ok(Boolean::False("\"\"".into()))); } #[test] fn from_str_true() { assert_eq!( Boolean::try_from("yes"), - Ok(Boolean::True(TrueVariant::Explicit("yes"))) + Ok(Boolean::True(TrueVariant::Explicit("yes".into()))) ); assert_eq!( Boolean::try_from("on"), - Ok(Boolean::True(TrueVariant::Explicit("on"))) + Ok(Boolean::True(TrueVariant::Explicit("on".into()))) ); assert_eq!( Boolean::try_from("true"), - Ok(Boolean::True(TrueVariant::Explicit("true"))) + Ok(Boolean::True(TrueVariant::Explicit("true".into()))) ); assert_eq!( Boolean::try_from("one"), - Ok(Boolean::True(TrueVariant::Explicit("one"))) + Ok(Boolean::True(TrueVariant::Explicit("one".into()))) ); }