Compare commits

..

No commits in common. "3ff68bfaf8a8a415d83301e7594744cdc81e053f" and "65744b0e13497f098ccbdd29c18d45e788379713" have entirely different histories.

7 changed files with 223 additions and 265 deletions

View file

@ -14,6 +14,7 @@ exclude = ["fuzz/**/*", ".vscode/**/*"]
serde = ["serde_crate"] serde = ["serde_crate"]
[dependencies] [dependencies]
bstr = "0.2.15"
nom = { version = "6", default_features = false, features = ["std"] } nom = { version = "6", default_features = false, features = ["std"] }
serde_crate = { version = "1", package = "serde", optional = true } serde_crate = { version = "1", package = "serde", optional = true }

View file

@ -1,17 +1,20 @@
use crate::parser::{parse_from_bytes, Event, ParsedSectionHeader, Parser, ParserError}; use crate::parser::{parse_from_bytes, Event, ParsedSectionHeader, Parser, ParserError};
use std::collections::{HashMap, VecDeque}; use bstr::BStr;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::error::Error;
use std::{borrow::Cow, fmt::Display}; use std::{borrow::Cow, fmt::Display};
use std::{
collections::{HashMap, VecDeque},
error::Error,
};
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] #[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
pub enum GitConfigError<'a> { pub enum GitConfigError<'a> {
/// The requested section does not exist. /// The requested section does not exist.
SectionDoesNotExist(&'a str), SectionDoesNotExist(&'a BStr),
/// The requested subsection does not exist. /// The requested subsection does not exist.
SubSectionDoesNotExist(Option<&'a str>), SubSectionDoesNotExist(Option<&'a BStr>),
/// The key does not exist in the requested section. /// The key does not exist in the requested section.
KeyDoesNotExist(&'a str), KeyDoesNotExist(&'a BStr),
FailedConversion, FailedConversion,
} }
@ -19,11 +22,8 @@ impl Display for GitConfigError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
// Todo, try parse as utf8 first for better looking errors // Todo, try parse as utf8 first for better looking errors
Self::SectionDoesNotExist(s) => write!(f, "Section '{}' does not exist.", s), Self::SectionDoesNotExist(s) => write!(f, "Subsection '{}' does not exist.", s),
Self::SubSectionDoesNotExist(s) => match s { Self::SubSectionDoesNotExist(s) => write!(f, "Subsection '{:?}' does not exist.", s),
Some(s) => write!(f, "Subsection '{}' does not exist.", s),
None => write!(f, "Top level section does not exist."),
},
Self::KeyDoesNotExist(k) => write!(f, "Name '{}' does not exist.", k), Self::KeyDoesNotExist(k) => write!(f, "Name '{}' does not exist.", k),
Self::FailedConversion => write!(f, "Failed to convert to specified type."), Self::FailedConversion => write!(f, "Failed to convert to specified type."),
} }
@ -49,7 +49,7 @@ struct SectionId(usize);
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
enum LookupTreeNode<'a> { enum LookupTreeNode<'a> {
Terminal(Vec<SectionId>), Terminal(Vec<SectionId>),
NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>), NonTerminal(HashMap<Cow<'a, BStr>, Vec<SectionId>>),
} }
/// High level `git-config` reader and writer. /// High level `git-config` reader and writer.
/// ///
@ -86,20 +86,20 @@ enum LookupTreeNode<'a> {
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # use std::convert::TryFrom; /// # use std::convert::TryFrom;
/// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # 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".into())));
/// ``` /// ```
/// ///
/// Consider the `multi` variants of the methods instead, if you want to work /// Consider the `multi` variants of the methods instead, if you want to work
/// with all values instead. /// with all values instead.
/// ///
/// [`get_raw_value`]: Self::get_raw_value /// [`get_value`]: Self::get_value
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug)]
pub struct GitConfig<'a> { pub struct GitConfig<'a> {
/// The list of events that occur before an actual section. Since 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 /// `git-config` file prohibits global values, this vec is limited to only
/// comment, newline, and whitespace events. /// comment, newline, and whitespace events.
front_matter_events: Vec<Event<'a>>, front_matter_events: Vec<Event<'a>>,
section_lookup_tree: HashMap<Cow<'a, str>, Vec<LookupTreeNode<'a>>>, section_lookup_tree: HashMap<Cow<'a, BStr>, Vec<LookupTreeNode<'a>>>,
/// SectionId to section mapping. The value of this HashMap contains actual /// SectionId to section mapping. The value of this HashMap contains actual
/// events /// events
sections: HashMap<SectionId, Vec<Event<'a>>>, sections: HashMap<SectionId, Vec<Event<'a>>>,
@ -111,8 +111,8 @@ pub struct GitConfig<'a> {
impl<'a> GitConfig<'a> { impl<'a> GitConfig<'a> {
fn push_section( fn push_section(
&mut self, &mut self,
current_section_name: Option<Cow<'a, str>>, current_section_name: Option<Cow<'a, BStr>>,
current_subsection_name: Option<Cow<'a, str>>, current_subsection_name: Option<Cow<'a, BStr>>,
maybe_section: &mut Option<Vec<Event<'a>>>, maybe_section: &mut Option<Vec<Event<'a>>>,
) { ) {
if let Some(section) = maybe_section.take() { if let Some(section) = maybe_section.take() {
@ -184,7 +184,7 @@ impl<'a> GitConfig<'a> {
/// "#; /// "#;
/// let git_config = GitConfig::try_from(config).unwrap(); /// let git_config = GitConfig::try_from(config).unwrap();
/// // You can either use the turbofish to determine the type... /// // You can either use the turbofish to determine the type...
/// let a_value = git_config.get_value::<Integer>("core", None, "a")?; /// let a_value = git_config.get_value::<Integer, _>("core", None, "a")?;
/// // ... or explicitly declare the type to avoid the turbofish /// // ... or explicitly declare the type to avoid the turbofish
/// let c_value: Boolean = git_config.get_value("core", None, "c")?; /// let c_value: Boolean = git_config.get_value("core", None, "c")?;
/// # Ok::<(), GitConfigError>(()) /// # Ok::<(), GitConfigError>(())
@ -198,11 +198,11 @@ impl<'a> GitConfig<'a> {
/// ///
/// [`values`]: crate::values /// [`values`]: crate::values
/// [`TryFrom`]: std::convert::TryFrom /// [`TryFrom`]: std::convert::TryFrom
pub fn get_value<'b, 'c, T: TryFrom<&'c [u8]>>( pub fn get_value<'b, 'c, T: TryFrom<&'c [u8]>, S: Into<&'b BStr>>(
&'c self, &'c self,
section_name: &'b str, section_name: S,
subsection_name: Option<&'b str>, subsection_name: Option<S>,
key: &'b str, key: S,
) -> Result<T, GitConfigError<'b>> { ) -> Result<T, GitConfigError<'b>> {
T::try_from(self.get_raw_value(section_name, subsection_name, key)?) T::try_from(self.get_raw_value(section_name, subsection_name, key)?)
.map_err(|_| GitConfigError::FailedConversion) .map_err(|_| GitConfigError::FailedConversion)
@ -210,8 +210,8 @@ impl<'a> GitConfig<'a> {
fn get_section_id_by_name_and_subname<'b>( fn get_section_id_by_name_and_subname<'b>(
&'a self, &'a self,
section_name: &'b str, section_name: &'b BStr,
subsection_name: Option<&'b str>, subsection_name: Option<&'b BStr>,
) -> Result<SectionId, GitConfigError<'b>> { ) -> Result<SectionId, GitConfigError<'b>> {
self.get_section_ids_by_name_and_subname(section_name, subsection_name) self.get_section_ids_by_name_and_subname(section_name, subsection_name)
.map(|vec| { .map(|vec| {
@ -231,17 +231,20 @@ impl<'a> GitConfig<'a> {
/// ///
/// This function will return an error if the key is not in the requested /// 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. /// section and subsection, or if the section and subsection do not exist.
pub fn get_raw_value<'b>( pub fn get_raw_value<'b, S: Into<&'b BStr>>(
&self, &self,
section_name: &'b str, section_name: S,
subsection_name: Option<&'b str>, subsection_name: Option<S>,
key: &'b str, key: S,
) -> Result<&Cow<'a, [u8]>, GitConfigError<'b>> { ) -> Result<&Cow<'a, BStr>, GitConfigError<'b>> {
let key = key; let key = key.into();
// Note: cannot wrap around the raw_multi_value method because we need // 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 // to guarantee that the highest section id is used (so that we follow
// the "last one wins" resolution strategy by `git-config`). // the "last one wins" resolution strategy by `git-config`).
let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?; let section_id = self.get_section_id_by_name_and_subname(
section_name.into(),
subsection_name.map(Into::into),
)?;
// section_id is guaranteed to exist in self.sections, else we have a // section_id is guaranteed to exist in self.sections, else we have a
// violated invariant. // violated invariant.
@ -272,17 +275,20 @@ impl<'a> GitConfig<'a> {
/// ///
/// This function will return an error if the key is not in the requested /// 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. /// section and subsection, or if the section and subsection do not exist.
pub fn get_raw_value_mut<'b>( pub fn get_raw_value_mut<'b, S: Into<&'b BStr>>(
&mut self, &mut self,
section_name: &'b str, section_name: S,
subsection_name: Option<&'b str>, subsection_name: Option<S>,
key: &'b str, key: S,
) -> Result<&mut Cow<'a, [u8]>, GitConfigError<'b>> { ) -> Result<&mut Cow<'a, BStr>, GitConfigError<'b>> {
let key = key; let key = key.into();
// Note: cannot wrap around the raw_multi_value method because we need // 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 // to guarantee that the highest section id is used (so that we follow
// the "last one wins" resolution strategy by `git-config`). // the "last one wins" resolution strategy by `git-config`).
let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?; let section_id = self.get_section_id_by_name_and_subname(
section_name.into(),
subsection_name.map(Into::into),
)?;
// section_id is guaranteed to exist in self.sections, else we have a // section_id is guaranteed to exist in self.sections, else we have a
// violated invariant. // violated invariant.
@ -323,11 +329,7 @@ impl<'a> GitConfig<'a> {
/// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
/// assert_eq!( /// assert_eq!(
/// git_config.get_raw_multi_value("core", None, "a"), /// git_config.get_raw_multi_value("core", None, "a"),
/// Ok(vec![ /// Ok(vec![&Cow::Borrowed("b".into()), &Cow::Borrowed("c".into()), &Cow::Borrowed("d".into())]),
/// &Cow::<[u8]>::Borrowed(b"b"),
/// &Cow::<[u8]>::Borrowed(b"c"),
/// &Cow::<[u8]>::Borrowed(b"d"),
/// ]),
/// ); /// );
/// ``` /// ```
/// ///
@ -339,15 +341,18 @@ impl<'a> GitConfig<'a> {
/// This function will return an error if the key is not in any requested /// This function will return an error if the key is not in any requested
/// section and subsection, or if no instance of the section and subsections /// section and subsection, or if no instance of the section and subsections
/// exist. /// exist.
pub fn get_raw_multi_value<'b>( pub fn get_raw_multi_value<'b, S: Into<&'b BStr>>(
&self, &'a self,
section_name: &'b str, section_name: S,
subsection_name: Option<&'b str>, subsection_name: Option<S>,
key: &'b str, key: S,
) -> Result<Vec<&Cow<'_, [u8]>>, GitConfigError<'b>> { ) -> Result<Vec<&Cow<'a, BStr>>, GitConfigError<'b>> {
let key = key; let key = key.into();
let mut values = vec![]; let mut values = vec![];
for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? { for section_id in self.get_section_ids_by_name_and_subname(
section_name.into(),
subsection_name.map(Into::into),
)? {
let mut found_key = false; let mut found_key = false;
// section_id is guaranteed to exist in self.sections, else we // section_id is guaranteed to exist in self.sections, else we
// have a violated invariant. // have a violated invariant.
@ -386,25 +391,26 @@ impl<'a> GitConfig<'a> {
/// ``` /// ```
/// # use git_config::config::{GitConfig, GitConfigError}; /// # use git_config::config::{GitConfig, GitConfigError};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # use bstr::BStr;
/// # use std::convert::TryFrom; /// # use std::convert::TryFrom;
/// # let mut git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # let mut git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
/// assert_eq!( /// assert_eq!(
/// git_config.get_raw_multi_value("core", None, "a")?, /// git_config.get_raw_multi_value("core", None, "a")?,
/// vec![ /// vec![
/// &Cow::Borrowed(b"b"), /// &Cow::<BStr>::Borrowed("b".into()),
/// &Cow::Borrowed(b"c"), /// &Cow::<BStr>::Borrowed("c".into()),
/// &Cow::Borrowed(b"d") /// &Cow::<BStr>::Borrowed("d".into())
/// ] /// ]
/// ); /// );
/// for value in git_config.get_raw_multi_value_mut("core", None, "a")? { /// for value in git_config.get_raw_multi_value_mut("core", None, "a")? {
/// *value = Cow::Borrowed(b"g"); /// *value = Cow::Borrowed("g".into());
///} ///}
/// assert_eq!( /// assert_eq!(
/// git_config.get_raw_multi_value("core", None, "a")?, /// git_config.get_raw_multi_value("core", None, "a")?,
/// vec![ /// vec![
/// &Cow::Borrowed(b"g"), /// &Cow::<BStr>::Borrowed("g".into()),
/// &Cow::Borrowed(b"g"), /// &Cow::<BStr>::Borrowed("g".into()),
/// &Cow::Borrowed(b"g") /// &Cow::<BStr>::Borrowed("g".into())
/// ], /// ],
/// ); /// );
/// # Ok::<(), GitConfigError>(()) /// # Ok::<(), GitConfigError>(())
@ -421,18 +427,21 @@ impl<'a> GitConfig<'a> {
/// This function will return an error if the key is not in any requested /// This function will return an error if the key is not in any requested
/// section and subsection, or if no instance of the section and subsections /// section and subsection, or if no instance of the section and subsections
/// exist. /// exist.
pub fn get_raw_multi_value_mut<'b>( pub fn get_raw_multi_value_mut<'b, S: Into<&'b BStr>>(
&mut self, &mut self,
section_name: &'b str, section_name: S,
subsection_name: Option<&'b str>, subsection_name: Option<S>,
key: &'b str, key: S,
) -> Result<Vec<&mut Cow<'a, [u8]>>, GitConfigError<'b>> { ) -> Result<Vec<&mut Cow<'a, BStr>>, GitConfigError<'b>> {
let key = key; let key = key.into();
let section_ids = self let section_ids = self
.get_section_ids_by_name_and_subname(section_name, subsection_name)? .get_section_ids_by_name_and_subname(
section_name.into(),
subsection_name.map(Into::into),
)?
.to_vec(); .to_vec();
let mut found_key = false; let mut found_key = false;
let values: Vec<&mut Cow<'a, [u8]>> = self let values: Vec<&mut Cow<'a, BStr>> = self
.sections .sections
.iter_mut() .iter_mut()
.filter_map(|(k, v)| { .filter_map(|(k, v)| {
@ -464,9 +473,9 @@ impl<'a> GitConfig<'a> {
} }
fn get_section_ids_by_name_and_subname<'b>( fn get_section_ids_by_name_and_subname<'b>(
&self, &'a self,
section_name: &'b str, section_name: &'b BStr,
subsection_name: Option<&'b str>, subsection_name: Option<&'b BStr>,
) -> Result<&[SectionId], GitConfigError<'b>> { ) -> Result<&[SectionId], GitConfigError<'b>> {
let section_ids = self let section_ids = self
.section_lookup_tree .section_lookup_tree
@ -496,12 +505,12 @@ impl<'a> GitConfig<'a> {
.ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name)) .ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name))
} }
pub fn set_raw_value<'b>( pub fn set_raw_value<'b, S: Into<&'b BStr>>(
&mut self, &mut self,
section_name: &'b str, section_name: S,
subsection_name: Option<&'b str>, subsection_name: Option<S>,
key: &'b str, key: S,
new_value: impl Into<Cow<'a, [u8]>>, new_value: impl Into<Cow<'a, BStr>>,
) -> Result<(), GitConfigError<'b>> { ) -> Result<(), GitConfigError<'b>> {
let value = self.get_raw_value_mut(section_name, subsection_name, key)?; let value = self.get_raw_value_mut(section_name, subsection_name, key)?;
*value = new_value.into(); *value = new_value.into();
@ -521,12 +530,12 @@ impl<'a> GitConfig<'a> {
/// todo: examples and errors /// todo: examples and errors
/// ///
/// [`get_raw_multi_value_mut`]: Self::get_raw_multi_value_mut /// [`get_raw_multi_value_mut`]: Self::get_raw_multi_value_mut
pub fn set_raw_multi_value<'b>( pub fn set_raw_multi_value<'b, S: Into<&'b BStr>>(
&mut self, &mut self,
section_name: &'b str, section_name: S,
subsection_name: Option<&'b str>, subsection_name: Option<S>,
key: &'b str, key: S,
new_values: Vec<Cow<'a, [u8]>>, new_values: Vec<Cow<'a, BStr>>,
) -> Result<(), GitConfigError<'b>> { ) -> Result<(), GitConfigError<'b>> {
let values = self.get_raw_multi_value_mut(section_name, subsection_name, key)?; let values = self.get_raw_multi_value_mut(section_name, subsection_name, key)?;
for (old, new) in values.into_iter().zip(new_values) { for (old, new) in values.into_iter().zip(new_values) {
@ -548,14 +557,14 @@ impl<'a> TryFrom<&'a str> for GitConfig<'a> {
} }
} }
impl<'a> TryFrom<&'a [u8]> for GitConfig<'a> { impl<'a> TryFrom<&'a BStr> for GitConfig<'a> {
type Error = ParserError<'a>; type Error = ParserError<'a>;
/// Convenience constructor. Attempts to parse the provided byte string into /// Convenience constructor. Attempts to parse the provided byte string into
//// a [`GitConfig`]. See [`parse_from_bytes`] for more information. //// a [`GitConfig`]. See [`parse_from_bytes`] for more information.
/// ///
/// [`parse_from_bytes`]: crate::parser::parse_from_bytes /// [`parse_from_bytes`]: crate::parser::parse_from_bytes
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> { fn try_from(value: &'a BStr) -> Result<Self, Self::Error> {
parse_from_bytes(value).map(Self::from) parse_from_bytes(value).map(Self::from)
} }
} }
@ -572,8 +581,8 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
}; };
// Current section that we're building // Current section that we're building
let mut current_section_name: Option<Cow<'a, str>> = None; let mut current_section_name: Option<Cow<'a, BStr>> = None;
let mut current_subsection_name: Option<Cow<'a, str>> = None; let mut current_subsection_name: Option<Cow<'a, BStr>> = None;
let mut maybe_section: Option<Vec<Event<'a>>> = None; let mut maybe_section: Option<Vec<Event<'a>>> = None;
for event in parser.into_iter() { for event in parser.into_iter() {
@ -627,9 +636,6 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
} }
impl Display for GitConfig<'_> { 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for front_matter in &self.front_matter_events { for front_matter in &self.front_matter_events {
front_matter.fmt(f)?; front_matter.fmt(f)?;
@ -676,7 +682,7 @@ mod from_parser {
let expected_lookup_tree = { let expected_lookup_tree = {
let mut tree = HashMap::new(); let mut tree = HashMap::new();
tree.insert( tree.insert(
Cow::Borrowed("core"), Cow::Borrowed("core".into()),
vec![LookupTreeNode::Terminal(vec![SectionId(0)])], vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
); );
tree tree
@ -716,9 +722,9 @@ mod from_parser {
let expected_lookup_tree = { let expected_lookup_tree = {
let mut tree = HashMap::new(); let mut tree = HashMap::new();
let mut inner_tree = HashMap::new(); let mut inner_tree = HashMap::new();
inner_tree.insert(Cow::Borrowed("subsec"), vec![SectionId(0)]); inner_tree.insert(Cow::Borrowed("subsec".into()), vec![SectionId(0)]);
tree.insert( tree.insert(
Cow::Borrowed("core"), Cow::Borrowed("core".into()),
vec![LookupTreeNode::NonTerminal(inner_tree)], vec![LookupTreeNode::NonTerminal(inner_tree)],
); );
tree tree
@ -759,11 +765,11 @@ mod from_parser {
let expected_lookup_tree = { let expected_lookup_tree = {
let mut tree = HashMap::new(); let mut tree = HashMap::new();
tree.insert( tree.insert(
Cow::Borrowed("core"), Cow::Borrowed("core".into()),
vec![LookupTreeNode::Terminal(vec![SectionId(0)])], vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
); );
tree.insert( tree.insert(
Cow::Borrowed("other"), Cow::Borrowed("other".into()),
vec![LookupTreeNode::Terminal(vec![SectionId(1)])], vec![LookupTreeNode::Terminal(vec![SectionId(1)])],
); );
tree tree
@ -812,7 +818,7 @@ mod from_parser {
let expected_lookup_tree = { let expected_lookup_tree = {
let mut tree = HashMap::new(); let mut tree = HashMap::new();
tree.insert( tree.insert(
Cow::Borrowed("core"), Cow::Borrowed("core".into()),
vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])], vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])],
); );
tree tree
@ -857,11 +863,11 @@ mod get_raw_value {
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_value("core", None, "a"), config.get_raw_value("core", None, "a"),
Ok(&Cow::<[u8]>::Borrowed(b"b")) Ok(&Cow::Borrowed("b".into()))
); );
assert_eq!( assert_eq!(
config.get_raw_value("core", None, "c"), config.get_raw_value("core", None, "c"),
Ok(&Cow::<[u8]>::Borrowed(b"d")) Ok(&Cow::Borrowed("d".into()))
); );
} }
@ -870,7 +876,7 @@ mod get_raw_value {
let config = GitConfig::try_from("[core]\na=b\na=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\na=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_value("core", None, "a"), config.get_raw_value("core", None, "a"),
Ok(&Cow::<[u8]>::Borrowed(b"d")) Ok(&Cow::Borrowed("d".into()))
); );
} }
@ -879,7 +885,7 @@ mod get_raw_value {
let config = GitConfig::try_from("[core]\na=b\n[core]\na=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\n[core]\na=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_value("core", None, "a"), config.get_raw_value("core", None, "a"),
Ok(&Cow::<[u8]>::Borrowed(b"d")) Ok(&Cow::Borrowed("d".into()))
); );
} }
@ -888,7 +894,7 @@ mod get_raw_value {
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_value("foo", None, "a"), config.get_raw_value("foo", None, "a"),
Err(GitConfigError::SectionDoesNotExist("foo")) Err(GitConfigError::SectionDoesNotExist("foo".into()))
); );
} }
@ -897,7 +903,7 @@ mod get_raw_value {
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_value("core", Some("a"), "a"), config.get_raw_value("core", Some("a"), "a"),
Err(GitConfigError::SubSectionDoesNotExist(Some("a"))) Err(GitConfigError::SubSectionDoesNotExist(Some("a".into())))
); );
} }
@ -906,7 +912,7 @@ mod get_raw_value {
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_value("core", None, "aaaaaa"), config.get_raw_value("core", None, "aaaaaa"),
Err(GitConfigError::KeyDoesNotExist("aaaaaa")) Err(GitConfigError::KeyDoesNotExist("aaaaaa".into()))
); );
} }
@ -915,11 +921,11 @@ mod get_raw_value {
let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap(); let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap();
assert_eq!( assert_eq!(
config.get_raw_value("core", None, "a"), config.get_raw_value("core", None, "a"),
Ok(&Cow::<[u8]>::Borrowed(b"b")) Ok(&Cow::Borrowed("b".into()))
); );
assert_eq!( assert_eq!(
config.get_raw_value("core", Some("a"), "a"), config.get_raw_value("core", Some("a"), "a"),
Ok(&Cow::<[u8]>::Borrowed(b"c")) Ok(&Cow::Borrowed("c".into()))
); );
} }
} }
@ -936,7 +942,7 @@ mod get_value {
let first_value: Value = config.get_value("core", None, "a")?; let first_value: Value = config.get_value("core", None, "a")?;
let second_value: Boolean = config.get_value("core", None, "c")?; let second_value: Boolean = config.get_value("core", None, "c")?;
assert_eq!(first_value, Value::Other(Cow::Borrowed(b"b"))); assert_eq!(first_value, Value::Other(Cow::Borrowed("b".into())));
assert_eq!(second_value, Boolean::True(TrueVariant::Implicit)); assert_eq!(second_value, Boolean::True(TrueVariant::Implicit));
Ok(()) Ok(())
@ -961,7 +967,7 @@ mod get_raw_multi_value {
let config = GitConfig::try_from("[core]\na=b\na=c").unwrap(); let config = GitConfig::try_from("[core]\na=b\na=c").unwrap();
assert_eq!( assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(), config.get_raw_multi_value("core", None, "a").unwrap(),
vec![&Cow::Borrowed(b"b"), &Cow::Borrowed(b"c")] vec![&Cow::Borrowed("b"), &Cow::Borrowed("c")]
); );
} }
@ -971,9 +977,9 @@ mod get_raw_multi_value {
assert_eq!( assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(), config.get_raw_multi_value("core", None, "a").unwrap(),
vec![ vec![
&Cow::Borrowed(b"b"), &Cow::Borrowed("b"),
&Cow::Borrowed(b"c"), &Cow::Borrowed("c"),
&Cow::Borrowed(b"d") &Cow::Borrowed("d")
] ]
); );
} }
@ -983,7 +989,7 @@ mod get_raw_multi_value {
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_multi_value("foo", None, "a"), config.get_raw_multi_value("foo", None, "a"),
Err(GitConfigError::SectionDoesNotExist("foo")) Err(GitConfigError::SectionDoesNotExist("foo".into()))
); );
} }
@ -992,7 +998,7 @@ mod get_raw_multi_value {
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_multi_value("core", Some("a"), "a"), config.get_raw_multi_value("core", Some("a"), "a"),
Err(GitConfigError::SubSectionDoesNotExist(Some("a"))) Err(GitConfigError::SubSectionDoesNotExist(Some("a".into())))
); );
} }
@ -1001,7 +1007,7 @@ mod get_raw_multi_value {
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap(); let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
assert_eq!( assert_eq!(
config.get_raw_multi_value("core", None, "aaaaaa"), config.get_raw_multi_value("core", None, "aaaaaa"),
Err(GitConfigError::KeyDoesNotExist("aaaaaa")) Err(GitConfigError::KeyDoesNotExist("aaaaaa".into()))
); );
} }
@ -1010,11 +1016,11 @@ mod get_raw_multi_value {
let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap(); let config = GitConfig::try_from("[core]a=b\n[core.a]a=c").unwrap();
assert_eq!( assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(), config.get_raw_multi_value("core", None, "a").unwrap(),
vec![&Cow::Borrowed(b"b")] vec![&Cow::Borrowed("b")]
); );
assert_eq!( assert_eq!(
config.get_raw_multi_value("core", Some("a"), "a").unwrap(), config.get_raw_multi_value("core", Some("a"), "a").unwrap(),
vec![&Cow::Borrowed(b"c")] vec![&Cow::Borrowed("c")]
); );
} }
@ -1024,9 +1030,9 @@ mod get_raw_multi_value {
assert_eq!( assert_eq!(
config.get_raw_multi_value("core", None, "a").unwrap(), config.get_raw_multi_value("core", None, "a").unwrap(),
vec![ vec![
&Cow::Borrowed(b"b"), &Cow::Borrowed("b"),
&Cow::Borrowed(b"c"), &Cow::Borrowed("c"),
&Cow::Borrowed(b"d") &Cow::Borrowed("d")
] ]
); );
} }

View file

@ -18,13 +18,13 @@ extern crate serde_crate as serde;
// mod de; // mod de;
// mod ser; // mod ser;
// mod error;
pub mod config; pub mod config;
mod error;
pub mod parser; pub mod parser;
pub mod values; pub mod values;
// pub use de::{from_str, Deserializer}; // pub use de::{from_str, Deserializer};
// pub use error::{Error, Result}; pub use error::{Error, Result};
// pub use ser::{to_string, Serializer}; // pub use ser::{to_string, Serializer};
#[cfg(test)] #[cfg(test)]

View file

@ -1,5 +1,5 @@
//! This module handles parsing a `git-config` file. Generally speaking, you //! This module handles parsing a `git-config` file. Generally speaking, you
//! want to use a higher a[u8]action such as [`GitConfig`] unless you have some //! want to use a higher abstraction such as [`GitConfig`] unless you have some
//! explicit reason to work with events instead. //! explicit reason to work with events instead.
//! //!
//! The general workflow for interacting with this is to use one of the //! The general workflow for interacting with this is to use one of the
@ -9,6 +9,7 @@
//! //!
//! [`GitConfig`]: crate::config::GitConfig //! [`GitConfig`]: crate::config::GitConfig
use bstr::{BStr, ByteSlice};
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::{escaped, tag, take_till, take_while}; use nom::bytes::complete::{escaped, tag, take_till, take_while};
use nom::character::complete::{char, none_of, one_of}; use nom::character::complete::{char, none_of, one_of};
@ -42,26 +43,26 @@ pub enum Event<'a> {
/// exists. /// exists.
SectionHeader(ParsedSectionHeader<'a>), SectionHeader(ParsedSectionHeader<'a>),
/// A name to a value in a section. /// A name to a value in a section.
Key(Cow<'a, str>), Key(Cow<'a, BStr>),
/// A completed value. This may be any string, including the empty string, /// A completed value. This may be any string, including the empty string,
/// if an implicit boolean value is used. Note that these values may contain /// if an implicit boolean value is used. Note that these values may contain
/// spaces and any special character. This value is also unprocessed, so it /// spaces and any special character. This value is also unprocessed, so it
/// it may contain double quotes that should be replaced. /// it may contain double quotes that should be replaced.
Value(Cow<'a, [u8]>), Value(Cow<'a, BStr>),
/// Represents any token used to signify a new line character. On Unix /// Represents any token used to signify a new line character. On Unix
/// platforms, this is typically just `\n`, but can be any valid newline /// platforms, this is typically just `\n`, but can be any valid newline
/// sequence. Multiple newlines (such as `\n\n`) will be merged as a single /// sequence. Multiple newlines (such as `\n\n`) will be merged as a single
/// newline event. /// newline event.
Newline(Cow<'a, str>), Newline(Cow<'a, BStr>),
/// Any value that isn't completed. This occurs when the value is continued /// Any value that isn't completed. This occurs when the value is continued
/// onto the next line. A Newline event is guaranteed after, followed by /// onto the next line. A Newline event is guaranteed after, followed by
/// either a ValueDone, a Whitespace, or another ValueNotDone. /// either a ValueDone, a Whitespace, or another ValueNotDone.
ValueNotDone(Cow<'a, [u8]>), ValueNotDone(Cow<'a, BStr>),
/// The last line of a value which was continued onto another line. /// The last line of a value which was continued onto another line.
ValueDone(Cow<'a, [u8]>), ValueDone(Cow<'a, BStr>),
/// A continuous section of insignificant whitespace. Values with internal /// A continuous section of insignificant whitespace. Values with internal
/// spaces will not be separated by this event. /// spaces will not be separated by this event.
Whitespace(Cow<'a, str>), Whitespace(Cow<'a, BStr>),
/// This event is emitted when the parser counters a valid `=` character /// This event is emitted when the parser counters a valid `=` character
/// separating the key and value. This event is necessary as it eliminates /// separating the key and value. This event is necessary as it eliminates
/// the ambiguity for whitespace events between a key and value event. /// the ambiguity for whitespace events between a key and value event.
@ -69,27 +70,15 @@ pub enum Event<'a> {
} }
impl Display for Event<'_> { 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
/// as read. Consider [`Event::as_bytes`] for one-to-one reading.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Comment(e) => e.fmt(f), Self::Comment(e) => e.fmt(f),
Self::SectionHeader(e) => e.fmt(f), Self::SectionHeader(e) => e.fmt(f),
Self::Key(e) => e.fmt(f), Self::Key(e) => e.fmt(f),
Self::Value(e) => match std::str::from_utf8(e) { Self::Value(e) => e.fmt(f),
Ok(e) => e.fmt(f),
Err(_) => write!(f, "{:02x?}", e),
},
Self::Newline(e) => e.fmt(f), Self::Newline(e) => e.fmt(f),
Self::ValueNotDone(e) => match std::str::from_utf8(e) { Self::ValueNotDone(e) => e.fmt(f),
Ok(e) => e.fmt(f), Self::ValueDone(e) => e.fmt(f),
Err(_) => write!(f, "{:02x?}", e),
},
Self::ValueDone(e) => match std::str::from_utf8(e) {
Ok(e) => e.fmt(f),
Err(_) => write!(f, "{:02x?}", e),
},
Self::Whitespace(e) => e.fmt(f), Self::Whitespace(e) => e.fmt(f),
Self::KeyValueSeparator => write!(f, "="), Self::KeyValueSeparator => write!(f, "="),
} }
@ -122,22 +111,18 @@ impl<'a> Into<Event<'a>> for ParsedSectionHeader<'a> {
} }
/// A parsed section header, containing a name and optionally a subsection name. /// 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
/// instances must also necessarily be valid UTF-8, which is why we use a
/// [`str`] instead of [`[u8]`].
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ParsedSectionHeader<'a> { pub struct ParsedSectionHeader<'a> {
/// The name of the header. /// The name of the header.
pub name: Cow<'a, str>, pub name: Cow<'a, BStr>,
/// The separator used to determine if the section contains a subsection. /// The separator used to determine if the section contains a subsection.
/// This is either a period `.` or a string of whitespace. Note that /// This is either a period `.` or a string of whitespace. Note that
/// reconstruction of subsection format is dependent on this value. If this /// reconstruction of subsection format is dependent on this value. If this
/// is all whitespace, then the subsection name needs to be surrounded by /// is all whitespace, then the subsection name needs to be surrounded by
/// quotes to have perfect reconstruction. /// quotes to have perfect reconstruction.
pub separator: Option<Cow<'a, str>>, pub separator: Option<Cow<'a, BStr>>,
/// The subsection name without quotes if any exist. /// The subsection name without quotes if any exist.
pub subsection_name: Option<Cow<'a, str>>, pub subsection_name: Option<Cow<'a, BStr>>,
} }
impl Display for ParsedSectionHeader<'_> { impl Display for ParsedSectionHeader<'_> {
@ -145,10 +130,9 @@ impl Display for ParsedSectionHeader<'_> {
write!(f, "[{}", self.name)?; write!(f, "[{}", self.name)?;
if let Some(v) = &self.separator { if let Some(v) = &self.separator {
// Separator must be utf-8
v.fmt(f)?; v.fmt(f)?;
let subsection_name = self.subsection_name.as_ref().unwrap(); let subsection_name = self.subsection_name.as_ref().unwrap();
if v == "." { if *v == b".".as_bstr() {
subsection_name.fmt(f)?; subsection_name.fmt(f)?;
} else { } else {
write!(f, "\"{}\"", subsection_name)?; write!(f, "\"{}\"", subsection_name)?;
@ -165,20 +149,13 @@ pub struct ParsedComment<'a> {
/// The comment marker used. This is either a semicolon or octothorpe. /// The comment marker used. This is either a semicolon or octothorpe.
pub comment_tag: char, pub comment_tag: char,
/// The parsed comment. /// The parsed comment.
pub comment: Cow<'a, [u8]>, pub comment: Cow<'a, BStr>,
} }
impl Display for ParsedComment<'_> { impl Display for ParsedComment<'_> {
/// Note that this is a best-effort attempt at printing an comment. If
/// there are non UTF-8 values in your config, this will _NOT_ render
/// as read. Consider [`Event::as_bytes`] for one-to-one reading.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.comment_tag.fmt(f)?; self.comment_tag.fmt(f)?;
if let Ok(s) = std::str::from_utf8(&self.comment) { self.comment.fmt(f)
s.fmt(f)
} else {
write!(f, "{:02x?}", self.comment)
}
} }
} }
@ -258,7 +235,7 @@ impl Display for ParserNode {
/// ///
/// This is parser exposes low-level syntactic events from a `git-config` file. /// This is parser exposes low-level syntactic events from a `git-config` file.
/// Generally speaking, you'll want to use [`GitConfig`] as it wraps /// 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. /// file, including querying, modifying, and updating values.
/// ///
/// This parser guarantees that the events emitted are sufficient to /// This parser guarantees that the events emitted are sufficient to
@ -331,20 +308,20 @@ impl Display for ParserNode {
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("core"), /// # name: Cow::Borrowed("core".into()),
/// # separator: None, /// # separator: None,
/// # subsection_name: None, /// # subsection_name: None,
/// # }; /// # };
/// # let section_data = "[core]\n autocrlf = input"; /// # let section_data = "[core]\n autocrlf = input";
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
/// Event::SectionHeader(section_header), /// Event::SectionHeader(section_header),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n".into())),
/// Event::Whitespace(Cow::Borrowed(" ")), /// Event::Whitespace(Cow::Borrowed(" ".into())),
/// Event::Key(Cow::Borrowed("autocrlf")), /// Event::Key(Cow::Borrowed("autocrlf".into())),
/// Event::Whitespace(Cow::Borrowed(" ")), /// Event::Whitespace(Cow::Borrowed(" ".into())),
/// Event::KeyValueSeparator, /// Event::KeyValueSeparator,
/// Event::Whitespace(Cow::Borrowed(" ")), /// Event::Whitespace(Cow::Borrowed(" ".into())),
/// Event::Value(Cow::Borrowed(b"input")), /// Event::Value(Cow::Borrowed("input".into())),
/// # ]); /// # ]);
/// ``` /// ```
/// ///
@ -369,18 +346,19 @@ impl Display for ParserNode {
/// ``` /// ```
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # use bstr::BStr;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("core"), /// # name: Cow::Borrowed("core".into()),
/// # separator: None, /// # separator: None,
/// # subsection_name: None, /// # subsection_name: None,
/// # }; /// # };
/// # let section_data = "[core]\n autocrlf"; /// # let section_data = "[core]\n autocrlf";
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
/// Event::SectionHeader(section_header), /// Event::SectionHeader(section_header),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n".into())),
/// Event::Whitespace(Cow::Borrowed(" ")), /// Event::Whitespace(Cow::Borrowed(" ".into())),
/// Event::Key(Cow::Borrowed("autocrlf")), /// Event::Key(Cow::Borrowed("autocrlf".into())),
/// Event::Value(Cow::Borrowed(b"")), /// Event::Value(Cow::Borrowed("".into())),
/// # ]); /// # ]);
/// ``` /// ```
/// ///
@ -404,21 +382,21 @@ impl Display for ParserNode {
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("core"), /// # name: Cow::Borrowed("core".into()),
/// # separator: None, /// # separator: None,
/// # subsection_name: None, /// # subsection_name: None,
/// # }; /// # };
/// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\""; /// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\"";
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
/// Event::SectionHeader(section_header), /// Event::SectionHeader(section_header),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n".into())),
/// Event::Key(Cow::Borrowed("autocrlf")), /// Event::Key(Cow::Borrowed("autocrlf".into())),
/// Event::KeyValueSeparator, /// Event::KeyValueSeparator,
/// Event::Value(Cow::Borrowed(br#"true"""#)), /// Event::Value(Cow::Borrowed(r#"true"""#.into())),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n".into())),
/// Event::Key(Cow::Borrowed("filemode")), /// Event::Key(Cow::Borrowed("filemode".into())),
/// Event::KeyValueSeparator, /// Event::KeyValueSeparator,
/// Event::Value(Cow::Borrowed(br#"fa"lse""#)), /// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())),
/// # ]); /// # ]);
/// ``` /// ```
/// ///
@ -441,19 +419,19 @@ impl Display for ParserNode {
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str}; /// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # let section_header = ParsedSectionHeader { /// # let section_header = ParsedSectionHeader {
/// # name: Cow::Borrowed("some-section"), /// # name: Cow::Borrowed("some-section".into()),
/// # separator: None, /// # separator: None,
/// # subsection_name: None, /// # subsection_name: None,
/// # }; /// # };
/// # let section_data = "[some-section]\nfile=a\\\n c"; /// # let section_data = "[some-section]\nfile=a\\\n c";
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![ /// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
/// Event::SectionHeader(section_header), /// Event::SectionHeader(section_header),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n".into())),
/// Event::Key(Cow::Borrowed("file")), /// Event::Key(Cow::Borrowed("file".into())),
/// Event::KeyValueSeparator, /// Event::KeyValueSeparator,
/// Event::ValueNotDone(Cow::Borrowed(b"a")), /// Event::ValueNotDone(Cow::Borrowed("a".into())),
/// Event::Newline(Cow::Borrowed("\n")), /// Event::Newline(Cow::Borrowed("\n".into())),
/// Event::ValueDone(Cow::Borrowed(b" c")), /// Event::ValueDone(Cow::Borrowed(" c".into())),
/// # ]); /// # ]);
/// ``` /// ```
/// ///
@ -509,7 +487,6 @@ impl<'a> Parser<'a> {
} }
/// Consumes the parser to produce an iterator of Events. /// Consumes the parser to produce an iterator of Events.
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn into_iter(self) -> impl Iterator<Item = Event<'a>> + FusedIterator { pub fn into_iter(self) -> impl Iterator<Item = Event<'a>> + FusedIterator {
// Can't impl IntoIter without allocating.and using a generic associated type // Can't impl IntoIter without allocating.and using a generic associated type
// TODO: try harder? // TODO: try harder?
@ -571,13 +548,13 @@ pub fn parse_from_bytes(input: &[u8]) -> Result<Parser<'_>, ParserError> {
let (i, frontmatter) = many0(alt(( let (i, frontmatter) = many0(alt((
map(comment, Event::Comment), map(comment, Event::Comment),
map(take_spaces, |whitespace| { map(take_spaces, |whitespace| {
Event::Whitespace(Cow::Borrowed(whitespace)) Event::Whitespace(Cow::Borrowed(whitespace.into()))
}), }),
map(take_newline, |(newline, counter)| { map(take_newline, |(newline, counter)| {
newlines += counter; newlines += counter;
Event::Newline(Cow::Borrowed(newline)) Event::Newline(Cow::Borrowed(newline.into()))
}), }),
)))(input) )))(input.as_bytes())
// I don't think this can panic. many0 errors if the child parser returns // I don't think this can panic. many0 errors if the child parser returns
// a success where the input was not consumed, but alt will only return Ok // a success where the input was not consumed, but alt will only return Ok
// if one of its children succeed. However, all of it's children are // if one of its children succeed. However, all of it's children are
@ -632,7 +609,7 @@ fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment> {
i, i,
ParsedComment { ParsedComment {
comment_tag, comment_tag,
comment: Cow::Borrowed(comment), comment: Cow::Borrowed(comment.into()),
}, },
)) ))
} }
@ -654,7 +631,7 @@ fn section<'a, 'b>(
if let Ok((new_i, v)) = take_spaces(i) { if let Ok((new_i, v)) = take_spaces(i) {
if old_i != new_i { if old_i != new_i {
i = new_i; i = new_i;
items.push(Event::Whitespace(Cow::Borrowed(v))); items.push(Event::Whitespace(Cow::Borrowed(v.into())));
} }
} }
@ -662,7 +639,7 @@ fn section<'a, 'b>(
if old_i != new_i { if old_i != new_i {
i = new_i; i = new_i;
newlines += new_newlines; newlines += new_newlines;
items.push(Event::Newline(Cow::Borrowed(v))); items.push(Event::Newline(Cow::Borrowed(v.into())));
} }
} }
@ -701,24 +678,21 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
// No spaces must be between section name and section start // No spaces must be between section name and section start
let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?; let (i, name) = take_while(|c: u8| c.is_ascii_alphanumeric() || c == b'-' || c == b'.')(i)?;
let name = std::str::from_utf8(name).map_err(|_| {
nom::Err::Error(NomError::<&[u8]> {
input: i,
code: ErrorKind::AlphaNumeric,
})
})?;
if let Ok((i, _)) = char::<_, NomError<&[u8]>>(']')(i) { if let Ok((i, _)) = char::<_, NomError<&[u8]>>(']')(i) {
// Either section does not have a subsection or using deprecated // Either section does not have a subsection or using deprecated
// subsection syntax at this point. // subsection syntax at this point.
let header = match find_legacy_subsection_separator(name) { let header = match name.rfind(&[b'.']) {
Some(index) => ParsedSectionHeader { Some(index) => ParsedSectionHeader {
name: Cow::Borrowed(&name[..index]), name: Cow::Borrowed(name[..index].into()),
separator: name.get(index..index + 1).map(|slice| Cow::Borrowed(slice)), separator: name
subsection_name: name.get(index + 1..).map(|slice| Cow::Borrowed(slice)), .get(index..index + 1)
.map(|slice| Cow::Borrowed(slice.into())),
subsection_name: name
.get(index + 1..)
.map(|slice| Cow::Borrowed(slice.into())),
}, },
None => ParsedSectionHeader { None => ParsedSectionHeader {
name: Cow::Borrowed(name), name: Cow::Borrowed(name.into()),
separator: None, separator: None,
subsection_name: None, subsection_name: None,
}, },
@ -736,38 +710,20 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
tag("\"]"), tag("\"]"),
)(i)?; )(i)?;
let subsection_name = subsection_name
.map(std::str::from_utf8)
.transpose()
.map_err(|_| {
nom::Err::Error(NomError::<&[u8]> {
input: i,
code: ErrorKind::AlphaNumeric,
})
})?;
Ok(( Ok((
i, i,
ParsedSectionHeader { ParsedSectionHeader {
name: Cow::Borrowed(name), name: Cow::Borrowed(name.into()),
separator: Some(Cow::Borrowed(whitespace)), separator: Some(Cow::Borrowed(whitespace.into())),
// We know that there's some section name here, so if we get an // We know that there's some section name here, so if we get an
// empty vec here then we actually parsed an empty section name. // empty vec here then we actually parsed an empty section name.
subsection_name: subsection_name.or(Some("")).map(Cow::Borrowed), subsection_name: subsection_name
.or(Some(b""))
.map(|slice| Cow::Borrowed(slice.into())),
}, },
)) ))
} }
fn find_legacy_subsection_separator(input: &str) -> Option<usize> {
let input = input.as_bytes();
for i in (0..input.len()).into_iter().rev() {
if input[i] == b'.' {
return Some(i);
}
}
None
}
fn section_body<'a, 'b, 'c>( fn section_body<'a, 'b, 'c>(
i: &'a [u8], i: &'a [u8],
node: &'b mut ParserNode, node: &'b mut ParserNode,
@ -777,12 +733,12 @@ fn section_body<'a, 'b, 'c>(
*node = ParserNode::ConfigName; *node = ParserNode::ConfigName;
let (i, name) = config_name(i)?; let (i, name) = config_name(i)?;
items.push(Event::Key(Cow::Borrowed(name))); items.push(Event::Key(Cow::Borrowed(name.into())));
let (i, whitespace) = opt(take_spaces)(i)?; let (i, whitespace) = opt(take_spaces)(i)?;
if let Some(whitespace) = whitespace { if let Some(whitespace) = whitespace {
items.push(Event::Whitespace(Cow::Borrowed(whitespace))); items.push(Event::Whitespace(Cow::Borrowed(whitespace.into())));
} }
let (i, _) = config_value(i, items)?; let (i, _) = config_value(i, items)?;
@ -791,7 +747,7 @@ fn section_body<'a, 'b, 'c>(
/// Parses the config name of a config pair. Assumes the input has already been /// Parses the config name of a config pair. Assumes the input has already been
/// trimmed of any leading whitespace. /// trimmed of any leading whitespace.
fn config_name(i: &[u8]) -> IResult<&[u8], &str> { fn config_name(i: &[u8]) -> IResult<&[u8], &[u8]> {
if i.is_empty() { if i.is_empty() {
return Err(nom::Err::Error(NomError { return Err(nom::Err::Error(NomError {
input: i, input: i,
@ -805,16 +761,7 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &str> {
code: ErrorKind::Alpha, code: ErrorKind::Alpha,
})); }));
} }
take_while(|c: u8| (c as char).is_alphanumeric() || c == b'-')(i)
let (i, v) = take_while(|c: u8| (c as char).is_alphanumeric() || c == b'-')(i)?;
let v = std::str::from_utf8(v).map_err(|_| {
nom::Err::Error(NomError::<&[u8]> {
input: i,
code: ErrorKind::AlphaNumeric,
})
})?;
Ok((i, v))
} }
fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<&'a [u8], ()> { fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<&'a [u8], ()> {
@ -822,12 +769,12 @@ fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<
events.push(Event::KeyValueSeparator); events.push(Event::KeyValueSeparator);
let (i, whitespace) = opt(take_spaces)(i)?; let (i, whitespace) = opt(take_spaces)(i)?;
if let Some(whitespace) = whitespace { if let Some(whitespace) = whitespace {
events.push(Event::Whitespace(Cow::Borrowed(whitespace))); events.push(Event::Whitespace(Cow::Borrowed(whitespace.into())));
} }
let (i, _) = value_impl(i, events)?; let (i, _) = value_impl(i, events)?;
Ok((i, ())) Ok((i, ()))
} else { } else {
events.push(Event::Value(Cow::Borrowed(b""))); events.push(Event::Value(Cow::Borrowed("".into())));
Ok((i, ())) Ok((i, ()))
} }
} }
@ -857,10 +804,10 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<&'
// continuation. // continuation.
b'\n' => { b'\n' => {
partial_value_found = true; partial_value_found = true;
events.push(Event::ValueNotDone(Cow::Borrowed(&i[offset..index - 1]))); events.push(Event::ValueNotDone(Cow::Borrowed(
events.push(Event::Newline(Cow::Borrowed( i[offset..index - 1].into(),
std::str::from_utf8(&i[index..index + 1]).unwrap(),
))); )));
events.push(Event::Newline(Cow::Borrowed(i[index..index + 1].into())));
offset = index + 1; offset = index + 1;
parsed_index = 0; parsed_index = 0;
} }
@ -921,15 +868,15 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<&'
}; };
if partial_value_found { if partial_value_found {
events.push(Event::ValueDone(Cow::Borrowed(remainder_value))); events.push(Event::ValueDone(Cow::Borrowed(remainder_value.into())));
} else { } else {
events.push(Event::Value(Cow::Borrowed(remainder_value))); events.push(Event::Value(Cow::Borrowed(remainder_value.into())));
} }
Ok((i, ())) Ok((i, ()))
} }
fn take_spaces(i: &[u8]) -> IResult<&[u8], &str> { fn take_spaces(i: &[u8]) -> IResult<&[u8], &[u8]> {
let (i, v) = take_while(|c| (c as char).is_ascii() && is_space(c))(i)?; let (i, v) = take_while(|c| (c as char).is_ascii() && is_space(c))(i)?;
if v.is_empty() { if v.is_empty() {
Err(nom::Err::Error(NomError { Err(nom::Err::Error(NomError {
@ -937,12 +884,11 @@ fn take_spaces(i: &[u8]) -> IResult<&[u8], &str> {
code: ErrorKind::Eof, code: ErrorKind::Eof,
})) }))
} else { } else {
// v is guaranteed to be utf-8 Ok((i, v))
Ok((i, std::str::from_utf8(v).unwrap()))
} }
} }
fn take_newline(i: &[u8]) -> IResult<&[u8], (&str, usize)> { fn take_newline(i: &[u8]) -> IResult<&[u8], (&[u8], usize)> {
let mut counter = 0; let mut counter = 0;
let (i, v) = take_while(|c| (c as char).is_ascii() && is_newline(c))(i)?; let (i, v) = take_while(|c| (c as char).is_ascii() && is_newline(c))(i)?;
counter += v.len(); counter += v.len();
@ -952,8 +898,7 @@ fn take_newline(i: &[u8]) -> IResult<&[u8], (&str, usize)> {
code: ErrorKind::Eof, code: ErrorKind::Eof,
})) }))
} else { } else {
// v is guaranteed to be utf-8 Ok((i, (v, counter)))
Ok((i, (std::str::from_utf8(v).unwrap(), counter)))
} }
} }
@ -1066,7 +1011,10 @@ mod config_name {
#[test] #[test]
fn just_name() { fn just_name() {
assert_eq!(config_name(b"name").unwrap(), fully_consumed("name")); assert_eq!(
config_name(b"name").unwrap(),
fully_consumed("name".as_bytes())
);
} }
#[test] #[test]

View file

@ -13,12 +13,12 @@ pub fn section_header(
name: &str, name: &str,
subsection: impl Into<Option<(&'static str, &'static str)>>, subsection: impl Into<Option<(&'static str, &'static str)>>,
) -> ParsedSectionHeader<'_> { ) -> ParsedSectionHeader<'_> {
let name = name.into(); let name = Cow::Borrowed(name.into());
if let Some((separator, subsection_name)) = subsection.into() { if let Some((separator, subsection_name)) = subsection.into() {
ParsedSectionHeader { ParsedSectionHeader {
name, name,
separator: Some(Cow::Borrowed(separator)), separator: Some(Cow::Borrowed(separator.into())),
subsection_name: Some(Cow::Borrowed(subsection_name)), subsection_name: Some(Cow::Borrowed(subsection_name.into())),
} }
} else { } else {
ParsedSectionHeader { ParsedSectionHeader {
@ -30,19 +30,19 @@ pub fn section_header(
} }
pub(crate) fn name_event(name: &'static str) -> Event<'static> { pub(crate) fn name_event(name: &'static str) -> Event<'static> {
Event::Key(Cow::Borrowed(name)) Event::Key(Cow::Borrowed(name.into()))
} }
pub(crate) fn value_event(value: &'static str) -> Event<'static> { pub(crate) fn value_event(value: &'static str) -> Event<'static> {
Event::Value(Cow::Borrowed(value.as_bytes())) Event::Value(Cow::Borrowed(value.into()))
} }
pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> { pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> {
Event::ValueNotDone(Cow::Borrowed(value.as_bytes())) Event::ValueNotDone(Cow::Borrowed(value.into()))
} }
pub(crate) fn value_done_event(value: &'static str) -> Event<'static> { pub(crate) fn value_done_event(value: &'static str) -> Event<'static> {
Event::ValueDone(Cow::Borrowed(value.as_bytes())) Event::ValueDone(Cow::Borrowed(value.into()))
} }
pub(crate) fn newline_event() -> Event<'static> { pub(crate) fn newline_event() -> Event<'static> {
@ -50,11 +50,11 @@ pub(crate) fn newline_event() -> Event<'static> {
} }
pub(crate) fn newline_custom_event(value: &'static str) -> Event<'static> { pub(crate) fn newline_custom_event(value: &'static str) -> Event<'static> {
Event::Newline(Cow::Borrowed(value)) Event::Newline(Cow::Borrowed(value.into()))
} }
pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> { pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> {
Event::Whitespace(Cow::Borrowed(value)) Event::Whitespace(Cow::Borrowed(value.into()))
} }
pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> { pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> {
@ -64,7 +64,7 @@ pub(crate) fn comment_event(tag: char, msg: &'static str) -> Event<'static> {
pub(crate) fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> { pub(crate) fn comment(comment_tag: char, comment: &'static str) -> ParsedComment<'static> {
ParsedComment { ParsedComment {
comment_tag, comment_tag,
comment: Cow::Borrowed(comment.as_bytes()), comment: Cow::Borrowed(comment.into()),
} }
} }

View file

@ -1,5 +1,6 @@
//! Rust containers for valid `git-config` types. //! Rust containers for valid `git-config` types.
use bstr::{BStr, ByteSlice};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use std::borrow::Cow; use std::borrow::Cow;
@ -120,7 +121,7 @@ pub enum Value<'a> {
/// If a value does not match from any of the other variants, then this /// If a value does not match from any of the other variants, then this
/// variant will be matched. As a result, conversion from a `str`-like item /// variant will be matched. As a result, conversion from a `str`-like item
/// will never fail. /// will never fail.
Other(Cow<'a, [u8]>), Other(Cow<'a, BStr>),
} }
impl<'a> From<&'a str> for Value<'a> { impl<'a> From<&'a str> for Value<'a> {
@ -137,7 +138,7 @@ impl<'a> From<&'a str> for Value<'a> {
return Self::Color(color); return Self::Color(color);
} }
Self::Other(Cow::Borrowed(s.as_bytes())) Self::Other(Cow::Borrowed(s.into()))
} }
} }
@ -147,7 +148,7 @@ impl<'a> From<&'a [u8]> for Value<'a> {
if let Ok(s) = std::str::from_utf8(s) { if let Ok(s) = std::str::from_utf8(s) {
Self::from(s) Self::from(s)
} else { } else {
Self::Other(Cow::Borrowed(s)) Self::Other(Cow::Borrowed(s.as_bstr()))
} }
} }
} }
@ -174,7 +175,9 @@ impl Serialize for Value<'_> {
/// Note that while values can effectively be any byte string, the `git-config` /// 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 /// 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. /// boolean value, all of which are ASCII and thus UTF-8 representable.
/// Consequently, variants hold [`str`]s rather than [`[u8]`]s. /// Consequently, variants hold [`str`]s rather than [`BStr`]s.
///
/// [`BStr`]: bstr::BStr
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum Boolean<'a> { pub enum Boolean<'a> {

View file

@ -34,7 +34,7 @@ fn name(name: &'static str) -> Event<'static> {
} }
fn value(value: &'static str) -> Event<'static> { fn value(value: &'static str) -> Event<'static> {
Event::Value(Cow::Borrowed(value.as_bytes())) Event::Value(Cow::Borrowed(value.into()))
} }
fn newline() -> Event<'static> { fn newline() -> Event<'static> {