diff --git a/src/file.rs b/src/file.rs index cb7417e..f1f73f3 100644 --- a/src/file.rs +++ b/src/file.rs @@ -313,7 +313,7 @@ pub struct GitConfig<'a> { /// The list of events that occur before an actual section. Since a /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. - front_matter_events: Vec>, + frontmatter_events: Vec>, section_lookup_tree: HashMap, Vec>>, /// SectionId to section mapping. The value of this HashMap contains actual /// events. @@ -365,12 +365,12 @@ impl<'event> GitConfig<'event> { /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom - pub fn get_value<'b, T: TryFrom>>( + pub fn get_value<'lookup, T: TryFrom>>( &'event self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - ) -> Result> { + section_name: &'lookup str, + subsection_name: Option<&'lookup str>, + key: &'lookup str, + ) -> Result> { T::try_from(self.get_raw_value(section_name, subsection_name, key)?) .map_err(|_| GitConfigError::FailedConversion) } @@ -425,12 +425,12 @@ impl<'event> GitConfig<'event> { /// /// [`values`]: crate::values /// [`TryFrom`]: std::convert::TryFrom - pub fn get_multi_value<'b, T: TryFrom>>( + pub fn get_multi_value<'lookup, T: TryFrom>>( &'event self, - section_name: &'b str, - subsection_name: Option<&'b str>, - key: &'b str, - ) -> Result, GitConfigError<'b>> { + section_name: &'lookup str, + subsection_name: Option<&'lookup str>, + key: &'lookup str, + ) -> Result, GitConfigError<'lookup>> { self.get_raw_multi_value(section_name, subsection_name, key)? .into_iter() .map(T::try_from) @@ -871,6 +871,35 @@ impl<'event> GitConfig<'event> { self.get_raw_multi_value_mut(section_name, subsection_name, key) .map(|mut v| v.set_values(new_values)) } + + /// Adds a new section to config. This cannot fail. + pub fn new_empty_section( + &mut self, + section_name: impl Into>, + subsection_name: impl Into>>, + ) { + self.push_section( + Some(section_name.into()), + subsection_name.into(), + &mut Some(vec![]), + ) + } + + /// Removes the section, returning the events it had, if any. + pub fn remove_section( + &mut self, + section_name: impl Into>, + subsection_name: impl Into>>, + ) -> Option> { + let mut section_ids = self + .get_section_ids_by_name_and_subname( + §ion_name.into(), + subsection_name.into().as_deref(), + ) + .ok()?; + + self.sections.remove(§ion_ids.pop()?) + } } /// Private helper functions @@ -990,7 +1019,7 @@ impl<'a> TryFrom<&'a [u8]> for GitConfig<'a> { impl<'a> From> for GitConfig<'a> { fn from(parser: Parser<'a>) -> Self { let mut new_self = Self { - front_matter_events: vec![], + frontmatter_events: vec![], sections: HashMap::new(), section_lookup_tree: HashMap::new(), section_headers: HashMap::new(), @@ -1035,7 +1064,7 @@ impl<'a> From> for GitConfig<'a> { e @ Event::Comment(_) | e @ Event::Newline(_) | e @ Event::Whitespace(_) => { match maybe_section { Some(ref mut section) => section.push(e), - None => new_self.front_matter_events.push(e), + None => new_self.frontmatter_events.push(e), } } } @@ -1053,12 +1082,37 @@ impl<'a> From> for GitConfig<'a> { } } +impl<'a> Into> for GitConfig<'a> { + fn into(self) -> Vec { + (&self).into() + } +} + +impl<'a> Into> for &GitConfig<'a> { + fn into(self) -> Vec { + let mut value = vec![]; + + for events in &self.frontmatter_events { + value.extend(events.to_vec()); + } + + for section_id in &self.section_order { + value.extend(self.section_headers.get(section_id).unwrap().to_vec()); + for event in self.sections.get(section_id).unwrap() { + value.extend(event.to_vec()); + } + } + + value + } +} + impl Display for GitConfig<'_> { /// Note that this is a best-effort attempt at printing a `GitConfig`. If /// there are non UTF-8 values in your config, this will _NOT_ render as /// read. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for front_matter in &self.front_matter_events { + for front_matter in &self.frontmatter_events { front_matter.fmt(f)?; } diff --git a/src/parser.rs b/src/parser.rs index 60d5969..fbc8d16 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -68,6 +68,12 @@ pub enum Event<'a> { KeyValueSeparator, } +impl Event<'_> { + pub fn to_vec(&self) -> Vec { + self.into() + } +} + impl Display for Event<'_> { /// Note that this is a best-effort attempt at printing an `Event`. If /// there are non UTF-8 values in your config, this will _NOT_ render @@ -88,6 +94,30 @@ impl Display for Event<'_> { } } +impl Into> for Event<'_> { + fn into(self) -> Vec { + match self { + Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => e.to_vec(), + Self::Comment(e) => e.into(), + Self::SectionHeader(e) => e.into(), + Self::Key(e) | Self::Newline(e) | Self::Whitespace(e) => e.as_bytes().to_vec(), + Self::KeyValueSeparator => vec![b'='], + } + } +} + +impl Into> for &Event<'_> { + fn into(self) -> Vec { + match self { + Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(), + Event::Comment(e) => e.into(), + Event::SectionHeader(e) => e.into(), + Event::Key(e) | Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(), + Event::KeyValueSeparator => vec![b'='], + } + } +} + /// A parsed section containing the header and the section events. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct ParsedSection<'a> { @@ -107,12 +137,6 @@ impl Display for ParsedSection<'_> { } } -impl<'a> Into> for ParsedSectionHeader<'a> { - fn into(self) -> Event<'a> { - Event::SectionHeader(self) - } -} - /// A parsed section header, containing a name and optionally a subsection name. /// /// Note that section headers must be parsed as valid ASCII, and thus all valid @@ -132,6 +156,12 @@ pub struct ParsedSectionHeader<'a> { pub subsection_name: Option>, } +impl ParsedSectionHeader<'_> { + pub fn to_vec(&self) -> Vec { + self.into() + } +} + impl Display for ParsedSectionHeader<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "[{}", self.name)?; @@ -151,6 +181,24 @@ impl Display for ParsedSectionHeader<'_> { } } +impl Into> for ParsedSectionHeader<'_> { + fn into(self) -> Vec { + (&self).into() + } +} + +impl Into> for &ParsedSectionHeader<'_> { + fn into(self) -> Vec { + self.to_string().into_bytes() + } +} + +impl<'a> Into> for ParsedSectionHeader<'a> { + fn into(self) -> Event<'a> { + Event::SectionHeader(self) + } +} + /// A parsed comment event containing the comment marker and comment. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct ParsedComment<'a> { @@ -174,6 +222,20 @@ impl Display for ParsedComment<'_> { } } +impl Into> for ParsedComment<'_> { + fn into(self) -> Vec { + (&self).into() + } +} + +impl Into> for &ParsedComment<'_> { + fn into(self) -> Vec { + let mut values = vec![self.comment_tag as u8]; + values.extend(self.comment.iter()); + values + } +} + /// A parser error reports the one-indexed line number where the parsing error /// occurred, as well as the last parser node and the remaining data to be /// parsed. @@ -250,7 +312,7 @@ impl Display for ParserNode { /// /// This is parser exposes low-level syntactic events from a `git-config` file. /// Generally speaking, you'll want to use [`GitConfig`] as it wraps -/// around the parser to provide a higher-level a[u8]action to a `git-config` +/// around the parser to provide a higher-level abstraction to a `git-config` /// file, including querying, modifying, and updating values. /// /// This parser guarantees that the events emitted are sufficient to diff --git a/src/values.rs b/src/values.rs index 5bb822d..d2b7c60 100644 --- a/src/values.rs +++ b/src/values.rs @@ -149,6 +149,12 @@ pub enum Value<'a> { Other(Cow<'a, [u8]>), } +impl Value<'_> { + pub fn to_vec(&self) -> Vec { + self.into() + } +} + impl<'a> From<&'a str> for Value<'a> { fn from(s: &'a str) -> Self { if let Ok(bool) = Boolean::try_from(s) { @@ -207,6 +213,23 @@ impl<'a> From> for Value<'a> { } } +impl Into> for Value<'_> { + fn into(self) -> Vec { + (&self).into() + } +} + +impl Into> for &Value<'_> { + fn into(self) -> Vec { + match self { + Value::Boolean(b) => b.into(), + Value::Integer(i) => i.into(), + Value::Color(c) => c.into(), + Value::Other(o) => o.to_vec(), + } + } +} + // todo display for value #[cfg(feature = "serde")] @@ -237,6 +260,16 @@ pub enum Boolean<'a> { False(Cow<'a, str>), } +impl Boolean<'_> { + pub fn to_vec(&self) -> Vec { + self.into() + } + + pub fn as_bytes(&self) -> &[u8] { + self.into() + } +} + impl<'a> TryFrom<&'a str> for Boolean<'a> { type Error = (); @@ -319,6 +352,27 @@ impl Into for Boolean<'_> { } } +impl<'a, 'b: 'a> Into<&'a [u8]> for &'b Boolean<'a> { + fn into(self) -> &'a [u8] { + match self { + Boolean::True(t) => t.into(), + Boolean::False(f) => f.as_bytes(), + } + } +} + +impl Into> for Boolean<'_> { + fn into(self) -> Vec { + (&self).into() + } +} + +impl Into> for &Boolean<'_> { + fn into(self) -> Vec { + self.to_string().into_bytes() + } +} + #[cfg(feature = "serde")] impl Serialize for Boolean<'_> { fn serialize(&self, serializer: S) -> Result @@ -407,6 +461,15 @@ impl Display for TrueVariant<'_> { } } +impl<'a, 'b: 'a> Into<&'a [u8]> for &'b TrueVariant<'a> { + fn into(self) -> &'a [u8] { + match self { + TrueVariant::Explicit(e) => e.as_bytes(), + TrueVariant::Implicit => &[], + } + } +} + #[cfg(feature = "serde")] impl Serialize for TrueVariant<'_> { fn serialize(&self, serializer: S) -> Result @@ -436,6 +499,12 @@ pub struct Integer { pub suffix: Option, } +impl Integer { + pub fn to_vec(&self) -> Vec { + self.into() + } +} + impl Display for Integer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.value)?; @@ -516,6 +585,18 @@ impl TryFrom> for Integer { } } +impl Into> for Integer { + fn into(self) -> Vec { + (&self).into() + } +} + +impl Into> for &Integer { + fn into(self) -> Vec { + self.to_string().into_bytes() + } +} + /// Integer prefixes that are supported by `git-config`. /// /// These values are base-2 unit of measurements, not the base-10 variants. @@ -608,6 +689,12 @@ pub struct Color { pub attributes: Vec, } +impl Color { + pub fn to_vec(&self) -> Vec { + self.into() + } +} + impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(fg) = self.foreground { @@ -715,6 +802,18 @@ impl TryFrom> for Color { } } +impl Into> for Color { + fn into(self) -> Vec { + (&self).into() + } +} + +impl Into> for &Color { + fn into(self) -> Vec { + self.to_string().into_bytes() + } +} + /// Discriminating enum for [`Color`] values. /// /// `git-config` supports the eight standard colors, their bright variants, an