Compare commits
2 commits
65744b0e13
...
3ff68bfaf8
Author | SHA1 | Date | |
---|---|---|---|
3ff68bfaf8 | |||
bfd4172e48 |
7 changed files with 265 additions and 223 deletions
|
@ -14,7 +14,6 @@ exclude = ["fuzz/**/*", ".vscode/**/*"]
|
|||
serde = ["serde_crate"]
|
||||
|
||||
[dependencies]
|
||||
bstr = "0.2.15"
|
||||
nom = { version = "6", default_features = false, features = ["std"] }
|
||||
serde_crate = { version = "1", package = "serde", optional = true }
|
||||
|
||||
|
|
232
src/config.rs
232
src/config.rs
|
@ -1,20 +1,17 @@
|
|||
use crate::parser::{parse_from_bytes, Event, ParsedSectionHeader, Parser, ParserError};
|
||||
use bstr::BStr;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
error::Error,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
|
||||
pub enum GitConfigError<'a> {
|
||||
/// The requested section does not exist.
|
||||
SectionDoesNotExist(&'a BStr),
|
||||
SectionDoesNotExist(&'a str),
|
||||
/// The requested subsection does not exist.
|
||||
SubSectionDoesNotExist(Option<&'a BStr>),
|
||||
SubSectionDoesNotExist(Option<&'a str>),
|
||||
/// The key does not exist in the requested section.
|
||||
KeyDoesNotExist(&'a BStr),
|
||||
KeyDoesNotExist(&'a str),
|
||||
FailedConversion,
|
||||
}
|
||||
|
||||
|
@ -22,8 +19,11 @@ impl Display for GitConfigError<'_> {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
// Todo, try parse as utf8 first for better looking errors
|
||||
Self::SectionDoesNotExist(s) => write!(f, "Subsection '{}' does not exist.", s),
|
||||
Self::SubSectionDoesNotExist(s) => write!(f, "Subsection '{:?}' does not exist.", s),
|
||||
Self::SectionDoesNotExist(s) => write!(f, "Section '{}' does not exist.", s),
|
||||
Self::SubSectionDoesNotExist(s) => match 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::FailedConversion => write!(f, "Failed to convert to specified type."),
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ struct SectionId(usize);
|
|||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
enum LookupTreeNode<'a> {
|
||||
Terminal(Vec<SectionId>),
|
||||
NonTerminal(HashMap<Cow<'a, BStr>, Vec<SectionId>>),
|
||||
NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>),
|
||||
}
|
||||
/// High level `git-config` reader and writer.
|
||||
///
|
||||
|
@ -86,20 +86,20 @@ 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".into())));
|
||||
/// 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
|
||||
/// with all values instead.
|
||||
///
|
||||
/// [`get_value`]: Self::get_value
|
||||
/// [`get_raw_value`]: Self::get_raw_value
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
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<Event<'a>>,
|
||||
section_lookup_tree: HashMap<Cow<'a, BStr>, Vec<LookupTreeNode<'a>>>,
|
||||
section_lookup_tree: HashMap<Cow<'a, str>, Vec<LookupTreeNode<'a>>>,
|
||||
/// SectionId to section mapping. The value of this HashMap contains actual
|
||||
/// events
|
||||
sections: HashMap<SectionId, Vec<Event<'a>>>,
|
||||
|
@ -111,8 +111,8 @@ pub struct GitConfig<'a> {
|
|||
impl<'a> GitConfig<'a> {
|
||||
fn push_section(
|
||||
&mut self,
|
||||
current_section_name: Option<Cow<'a, BStr>>,
|
||||
current_subsection_name: Option<Cow<'a, BStr>>,
|
||||
current_section_name: Option<Cow<'a, str>>,
|
||||
current_subsection_name: Option<Cow<'a, str>>,
|
||||
maybe_section: &mut Option<Vec<Event<'a>>>,
|
||||
) {
|
||||
if let Some(section) = maybe_section.take() {
|
||||
|
@ -184,7 +184,7 @@ impl<'a> GitConfig<'a> {
|
|||
/// "#;
|
||||
/// let git_config = GitConfig::try_from(config).unwrap();
|
||||
/// // 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
|
||||
/// let c_value: Boolean = git_config.get_value("core", None, "c")?;
|
||||
/// # Ok::<(), GitConfigError>(())
|
||||
|
@ -198,11 +198,11 @@ impl<'a> GitConfig<'a> {
|
|||
///
|
||||
/// [`values`]: crate::values
|
||||
/// [`TryFrom`]: std::convert::TryFrom
|
||||
pub fn get_value<'b, 'c, T: TryFrom<&'c [u8]>, S: Into<&'b BStr>>(
|
||||
pub fn get_value<'b, 'c, T: TryFrom<&'c [u8]>>(
|
||||
&'c self,
|
||||
section_name: S,
|
||||
subsection_name: Option<S>,
|
||||
key: S,
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
key: &'b str,
|
||||
) -> Result<T, GitConfigError<'b>> {
|
||||
T::try_from(self.get_raw_value(section_name, subsection_name, key)?)
|
||||
.map_err(|_| GitConfigError::FailedConversion)
|
||||
|
@ -210,8 +210,8 @@ impl<'a> GitConfig<'a> {
|
|||
|
||||
fn get_section_id_by_name_and_subname<'b>(
|
||||
&'a self,
|
||||
section_name: &'b BStr,
|
||||
subsection_name: Option<&'b BStr>,
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
) -> Result<SectionId, GitConfigError<'b>> {
|
||||
self.get_section_ids_by_name_and_subname(section_name, subsection_name)
|
||||
.map(|vec| {
|
||||
|
@ -231,20 +231,17 @@ 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, S: Into<&'b BStr>>(
|
||||
pub fn get_raw_value<'b>(
|
||||
&self,
|
||||
section_name: S,
|
||||
subsection_name: Option<S>,
|
||||
key: S,
|
||||
) -> Result<&Cow<'a, BStr>, GitConfigError<'b>> {
|
||||
let key = key.into();
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
key: &'b str,
|
||||
) -> Result<&Cow<'a, [u8]>, 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.into(),
|
||||
subsection_name.map(Into::into),
|
||||
)?;
|
||||
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.
|
||||
|
@ -275,20 +272,17 @@ 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_mut<'b, S: Into<&'b BStr>>(
|
||||
pub fn get_raw_value_mut<'b>(
|
||||
&mut self,
|
||||
section_name: S,
|
||||
subsection_name: Option<S>,
|
||||
key: S,
|
||||
) -> Result<&mut Cow<'a, BStr>, GitConfigError<'b>> {
|
||||
let key = key.into();
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
key: &'b str,
|
||||
) -> Result<&mut Cow<'a, [u8]>, 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.into(),
|
||||
subsection_name.map(Into::into),
|
||||
)?;
|
||||
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.
|
||||
|
@ -329,7 +323,11 @@ impl<'a> GitConfig<'a> {
|
|||
/// # let git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
|
||||
/// assert_eq!(
|
||||
/// git_config.get_raw_multi_value("core", None, "a"),
|
||||
/// Ok(vec![&Cow::Borrowed("b".into()), &Cow::Borrowed("c".into()), &Cow::Borrowed("d".into())]),
|
||||
/// Ok(vec![
|
||||
/// &Cow::<[u8]>::Borrowed(b"b"),
|
||||
/// &Cow::<[u8]>::Borrowed(b"c"),
|
||||
/// &Cow::<[u8]>::Borrowed(b"d"),
|
||||
/// ]),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
|
@ -341,18 +339,15 @@ impl<'a> GitConfig<'a> {
|
|||
/// 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
|
||||
/// exist.
|
||||
pub fn get_raw_multi_value<'b, S: Into<&'b BStr>>(
|
||||
&'a self,
|
||||
section_name: S,
|
||||
subsection_name: Option<S>,
|
||||
key: S,
|
||||
) -> Result<Vec<&Cow<'a, BStr>>, GitConfigError<'b>> {
|
||||
let key = key.into();
|
||||
pub fn get_raw_multi_value<'b>(
|
||||
&self,
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
key: &'b str,
|
||||
) -> Result<Vec<&Cow<'_, [u8]>>, GitConfigError<'b>> {
|
||||
let key = key;
|
||||
let mut values = vec![];
|
||||
for section_id in self.get_section_ids_by_name_and_subname(
|
||||
section_name.into(),
|
||||
subsection_name.map(Into::into),
|
||||
)? {
|
||||
for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? {
|
||||
let mut found_key = false;
|
||||
// section_id is guaranteed to exist in self.sections, else we
|
||||
// have a violated invariant.
|
||||
|
@ -391,26 +386,25 @@ impl<'a> GitConfig<'a> {
|
|||
/// ```
|
||||
/// # use git_config::config::{GitConfig, GitConfigError};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # use bstr::BStr;
|
||||
/// # use std::convert::TryFrom;
|
||||
/// # let mut git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
|
||||
/// assert_eq!(
|
||||
/// git_config.get_raw_multi_value("core", None, "a")?,
|
||||
/// vec![
|
||||
/// &Cow::<BStr>::Borrowed("b".into()),
|
||||
/// &Cow::<BStr>::Borrowed("c".into()),
|
||||
/// &Cow::<BStr>::Borrowed("d".into())
|
||||
/// &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")? {
|
||||
/// *value = Cow::Borrowed("g".into());
|
||||
/// *value = Cow::Borrowed(b"g");
|
||||
///}
|
||||
/// assert_eq!(
|
||||
/// git_config.get_raw_multi_value("core", None, "a")?,
|
||||
/// vec![
|
||||
/// &Cow::<BStr>::Borrowed("g".into()),
|
||||
/// &Cow::<BStr>::Borrowed("g".into()),
|
||||
/// &Cow::<BStr>::Borrowed("g".into())
|
||||
/// &Cow::Borrowed(b"g"),
|
||||
/// &Cow::Borrowed(b"g"),
|
||||
/// &Cow::Borrowed(b"g")
|
||||
/// ],
|
||||
/// );
|
||||
/// # Ok::<(), GitConfigError>(())
|
||||
|
@ -427,21 +421,18 @@ impl<'a> GitConfig<'a> {
|
|||
/// 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
|
||||
/// exist.
|
||||
pub fn get_raw_multi_value_mut<'b, S: Into<&'b BStr>>(
|
||||
pub fn get_raw_multi_value_mut<'b>(
|
||||
&mut self,
|
||||
section_name: S,
|
||||
subsection_name: Option<S>,
|
||||
key: S,
|
||||
) -> Result<Vec<&mut Cow<'a, BStr>>, GitConfigError<'b>> {
|
||||
let key = key.into();
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
key: &'b str,
|
||||
) -> Result<Vec<&mut Cow<'a, [u8]>>, GitConfigError<'b>> {
|
||||
let key = key;
|
||||
let section_ids = self
|
||||
.get_section_ids_by_name_and_subname(
|
||||
section_name.into(),
|
||||
subsection_name.map(Into::into),
|
||||
)?
|
||||
.get_section_ids_by_name_and_subname(section_name, subsection_name)?
|
||||
.to_vec();
|
||||
let mut found_key = false;
|
||||
let values: Vec<&mut Cow<'a, BStr>> = self
|
||||
let values: Vec<&mut Cow<'a, [u8]>> = self
|
||||
.sections
|
||||
.iter_mut()
|
||||
.filter_map(|(k, v)| {
|
||||
|
@ -473,9 +464,9 @@ impl<'a> GitConfig<'a> {
|
|||
}
|
||||
|
||||
fn get_section_ids_by_name_and_subname<'b>(
|
||||
&'a self,
|
||||
section_name: &'b BStr,
|
||||
subsection_name: Option<&'b BStr>,
|
||||
&self,
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
) -> Result<&[SectionId], GitConfigError<'b>> {
|
||||
let section_ids = self
|
||||
.section_lookup_tree
|
||||
|
@ -505,12 +496,12 @@ impl<'a> GitConfig<'a> {
|
|||
.ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name))
|
||||
}
|
||||
|
||||
pub fn set_raw_value<'b, S: Into<&'b BStr>>(
|
||||
pub fn set_raw_value<'b>(
|
||||
&mut self,
|
||||
section_name: S,
|
||||
subsection_name: Option<S>,
|
||||
key: S,
|
||||
new_value: impl Into<Cow<'a, BStr>>,
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
key: &'b str,
|
||||
new_value: impl Into<Cow<'a, [u8]>>,
|
||||
) -> Result<(), GitConfigError<'b>> {
|
||||
let value = self.get_raw_value_mut(section_name, subsection_name, key)?;
|
||||
*value = new_value.into();
|
||||
|
@ -530,12 +521,12 @@ impl<'a> GitConfig<'a> {
|
|||
/// todo: examples and errors
|
||||
///
|
||||
/// [`get_raw_multi_value_mut`]: Self::get_raw_multi_value_mut
|
||||
pub fn set_raw_multi_value<'b, S: Into<&'b BStr>>(
|
||||
pub fn set_raw_multi_value<'b>(
|
||||
&mut self,
|
||||
section_name: S,
|
||||
subsection_name: Option<S>,
|
||||
key: S,
|
||||
new_values: Vec<Cow<'a, BStr>>,
|
||||
section_name: &'b str,
|
||||
subsection_name: Option<&'b str>,
|
||||
key: &'b str,
|
||||
new_values: Vec<Cow<'a, [u8]>>,
|
||||
) -> Result<(), GitConfigError<'b>> {
|
||||
let values = self.get_raw_multi_value_mut(section_name, subsection_name, key)?;
|
||||
for (old, new) in values.into_iter().zip(new_values) {
|
||||
|
@ -557,14 +548,14 @@ impl<'a> TryFrom<&'a str> for GitConfig<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a BStr> for GitConfig<'a> {
|
||||
impl<'a> TryFrom<&'a [u8]> for GitConfig<'a> {
|
||||
type Error = ParserError<'a>;
|
||||
|
||||
/// Convenience constructor. Attempts to parse the provided byte string into
|
||||
//// a [`GitConfig`]. See [`parse_from_bytes`] for more information.
|
||||
///
|
||||
/// [`parse_from_bytes`]: crate::parser::parse_from_bytes
|
||||
fn try_from(value: &'a BStr) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
|
||||
parse_from_bytes(value).map(Self::from)
|
||||
}
|
||||
}
|
||||
|
@ -581,8 +572,8 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
|
|||
};
|
||||
|
||||
// Current section that we're building
|
||||
let mut current_section_name: Option<Cow<'a, BStr>> = None;
|
||||
let mut current_subsection_name: Option<Cow<'a, BStr>> = None;
|
||||
let mut current_section_name: Option<Cow<'a, str>> = None;
|
||||
let mut current_subsection_name: Option<Cow<'a, str>> = None;
|
||||
let mut maybe_section: Option<Vec<Event<'a>>> = None;
|
||||
|
||||
for event in parser.into_iter() {
|
||||
|
@ -636,6 +627,9 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
|
|||
}
|
||||
|
||||
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 {
|
||||
front_matter.fmt(f)?;
|
||||
|
@ -682,7 +676,7 @@ mod from_parser {
|
|||
let expected_lookup_tree = {
|
||||
let mut tree = HashMap::new();
|
||||
tree.insert(
|
||||
Cow::Borrowed("core".into()),
|
||||
Cow::Borrowed("core"),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
||||
);
|
||||
tree
|
||||
|
@ -722,9 +716,9 @@ mod from_parser {
|
|||
let expected_lookup_tree = {
|
||||
let mut tree = HashMap::new();
|
||||
let mut inner_tree = HashMap::new();
|
||||
inner_tree.insert(Cow::Borrowed("subsec".into()), vec![SectionId(0)]);
|
||||
inner_tree.insert(Cow::Borrowed("subsec"), vec![SectionId(0)]);
|
||||
tree.insert(
|
||||
Cow::Borrowed("core".into()),
|
||||
Cow::Borrowed("core"),
|
||||
vec![LookupTreeNode::NonTerminal(inner_tree)],
|
||||
);
|
||||
tree
|
||||
|
@ -765,11 +759,11 @@ mod from_parser {
|
|||
let expected_lookup_tree = {
|
||||
let mut tree = HashMap::new();
|
||||
tree.insert(
|
||||
Cow::Borrowed("core".into()),
|
||||
Cow::Borrowed("core"),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
||||
);
|
||||
tree.insert(
|
||||
Cow::Borrowed("other".into()),
|
||||
Cow::Borrowed("other"),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(1)])],
|
||||
);
|
||||
tree
|
||||
|
@ -818,7 +812,7 @@ mod from_parser {
|
|||
let expected_lookup_tree = {
|
||||
let mut tree = HashMap::new();
|
||||
tree.insert(
|
||||
Cow::Borrowed("core".into()),
|
||||
Cow::Borrowed("core"),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])],
|
||||
);
|
||||
tree
|
||||
|
@ -863,11 +857,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::Borrowed("b".into()))
|
||||
Ok(&Cow::<[u8]>::Borrowed(b"b"))
|
||||
);
|
||||
assert_eq!(
|
||||
config.get_raw_value("core", None, "c"),
|
||||
Ok(&Cow::Borrowed("d".into()))
|
||||
Ok(&Cow::<[u8]>::Borrowed(b"d"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -876,7 +870,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::Borrowed("d".into()))
|
||||
Ok(&Cow::<[u8]>::Borrowed(b"d"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -885,7 +879,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::Borrowed("d".into()))
|
||||
Ok(&Cow::<[u8]>::Borrowed(b"d"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -894,7 +888,7 @@ mod get_raw_value {
|
|||
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||
assert_eq!(
|
||||
config.get_raw_value("foo", None, "a"),
|
||||
Err(GitConfigError::SectionDoesNotExist("foo".into()))
|
||||
Err(GitConfigError::SectionDoesNotExist("foo"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -903,7 +897,7 @@ mod get_raw_value {
|
|||
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||
assert_eq!(
|
||||
config.get_raw_value("core", Some("a"), "a"),
|
||||
Err(GitConfigError::SubSectionDoesNotExist(Some("a".into())))
|
||||
Err(GitConfigError::SubSectionDoesNotExist(Some("a")))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -912,7 +906,7 @@ mod get_raw_value {
|
|||
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||
assert_eq!(
|
||||
config.get_raw_value("core", None, "aaaaaa"),
|
||||
Err(GitConfigError::KeyDoesNotExist("aaaaaa".into()))
|
||||
Err(GitConfigError::KeyDoesNotExist("aaaaaa"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -921,11 +915,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::Borrowed("b".into()))
|
||||
Ok(&Cow::<[u8]>::Borrowed(b"b"))
|
||||
);
|
||||
assert_eq!(
|
||||
config.get_raw_value("core", Some("a"), "a"),
|
||||
Ok(&Cow::Borrowed("c".into()))
|
||||
Ok(&Cow::<[u8]>::Borrowed(b"c"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -942,7 +936,7 @@ mod get_value {
|
|||
let first_value: Value = config.get_value("core", None, "a")?;
|
||||
let second_value: Boolean = config.get_value("core", None, "c")?;
|
||||
|
||||
assert_eq!(first_value, Value::Other(Cow::Borrowed("b".into())));
|
||||
assert_eq!(first_value, Value::Other(Cow::Borrowed(b"b")));
|
||||
assert_eq!(second_value, Boolean::True(TrueVariant::Implicit));
|
||||
|
||||
Ok(())
|
||||
|
@ -967,7 +961,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"), &Cow::Borrowed("c")]
|
||||
vec![&Cow::Borrowed(b"b"), &Cow::Borrowed(b"c")]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -977,9 +971,9 @@ mod get_raw_multi_value {
|
|||
assert_eq!(
|
||||
config.get_raw_multi_value("core", None, "a").unwrap(),
|
||||
vec![
|
||||
&Cow::Borrowed("b"),
|
||||
&Cow::Borrowed("c"),
|
||||
&Cow::Borrowed("d")
|
||||
&Cow::Borrowed(b"b"),
|
||||
&Cow::Borrowed(b"c"),
|
||||
&Cow::Borrowed(b"d")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -989,7 +983,7 @@ mod get_raw_multi_value {
|
|||
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||
assert_eq!(
|
||||
config.get_raw_multi_value("foo", None, "a"),
|
||||
Err(GitConfigError::SectionDoesNotExist("foo".into()))
|
||||
Err(GitConfigError::SectionDoesNotExist("foo"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -998,7 +992,7 @@ mod get_raw_multi_value {
|
|||
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||
assert_eq!(
|
||||
config.get_raw_multi_value("core", Some("a"), "a"),
|
||||
Err(GitConfigError::SubSectionDoesNotExist(Some("a".into())))
|
||||
Err(GitConfigError::SubSectionDoesNotExist(Some("a")))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1007,7 +1001,7 @@ mod get_raw_multi_value {
|
|||
let config = GitConfig::try_from("[core]\na=b\nc=d").unwrap();
|
||||
assert_eq!(
|
||||
config.get_raw_multi_value("core", None, "aaaaaa"),
|
||||
Err(GitConfigError::KeyDoesNotExist("aaaaaa".into()))
|
||||
Err(GitConfigError::KeyDoesNotExist("aaaaaa"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1016,11 +1010,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")]
|
||||
vec![&Cow::Borrowed(b"b")]
|
||||
);
|
||||
assert_eq!(
|
||||
config.get_raw_multi_value("core", Some("a"), "a").unwrap(),
|
||||
vec![&Cow::Borrowed("c")]
|
||||
vec![&Cow::Borrowed(b"c")]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1030,9 +1024,9 @@ mod get_raw_multi_value {
|
|||
assert_eq!(
|
||||
config.get_raw_multi_value("core", None, "a").unwrap(),
|
||||
vec![
|
||||
&Cow::Borrowed("b"),
|
||||
&Cow::Borrowed("c"),
|
||||
&Cow::Borrowed("d")
|
||||
&Cow::Borrowed(b"b"),
|
||||
&Cow::Borrowed(b"c"),
|
||||
&Cow::Borrowed(b"d")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,13 +18,13 @@ extern crate serde_crate as serde;
|
|||
|
||||
// mod de;
|
||||
// mod ser;
|
||||
// mod error;
|
||||
pub mod config;
|
||||
mod error;
|
||||
pub mod parser;
|
||||
pub mod values;
|
||||
|
||||
// pub use de::{from_str, Deserializer};
|
||||
pub use error::{Error, Result};
|
||||
// pub use error::{Error, Result};
|
||||
// pub use ser::{to_string, Serializer};
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
218
src/parser.rs
218
src/parser.rs
|
@ -1,5 +1,5 @@
|
|||
//! This module handles parsing a `git-config` file. Generally speaking, you
|
||||
//! want to use a higher abstraction such as [`GitConfig`] unless you have some
|
||||
//! want to use a higher a[u8]action such as [`GitConfig`] unless you have some
|
||||
//! explicit reason to work with events instead.
|
||||
//!
|
||||
//! The general workflow for interacting with this is to use one of the
|
||||
|
@ -9,7 +9,6 @@
|
|||
//!
|
||||
//! [`GitConfig`]: crate::config::GitConfig
|
||||
|
||||
use bstr::{BStr, ByteSlice};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{escaped, tag, take_till, take_while};
|
||||
use nom::character::complete::{char, none_of, one_of};
|
||||
|
@ -43,26 +42,26 @@ pub enum Event<'a> {
|
|||
/// exists.
|
||||
SectionHeader(ParsedSectionHeader<'a>),
|
||||
/// A name to a value in a section.
|
||||
Key(Cow<'a, BStr>),
|
||||
Key(Cow<'a, str>),
|
||||
/// 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
|
||||
/// spaces and any special character. This value is also unprocessed, so it
|
||||
/// it may contain double quotes that should be replaced.
|
||||
Value(Cow<'a, BStr>),
|
||||
Value(Cow<'a, [u8]>),
|
||||
/// Represents any token used to signify a new line character. On Unix
|
||||
/// 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
|
||||
/// newline event.
|
||||
Newline(Cow<'a, BStr>),
|
||||
Newline(Cow<'a, str>),
|
||||
/// 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
|
||||
/// either a ValueDone, a Whitespace, or another ValueNotDone.
|
||||
ValueNotDone(Cow<'a, BStr>),
|
||||
ValueNotDone(Cow<'a, [u8]>),
|
||||
/// The last line of a value which was continued onto another line.
|
||||
ValueDone(Cow<'a, BStr>),
|
||||
ValueDone(Cow<'a, [u8]>),
|
||||
/// A continuous section of insignificant whitespace. Values with internal
|
||||
/// spaces will not be separated by this event.
|
||||
Whitespace(Cow<'a, BStr>),
|
||||
Whitespace(Cow<'a, str>),
|
||||
/// This event is emitted when the parser counters a valid `=` character
|
||||
/// separating the key and value. This event is necessary as it eliminates
|
||||
/// the ambiguity for whitespace events between a key and value event.
|
||||
|
@ -70,15 +69,27 @@ pub enum Event<'a> {
|
|||
}
|
||||
|
||||
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 {
|
||||
match self {
|
||||
Self::Comment(e) => e.fmt(f),
|
||||
Self::SectionHeader(e) => e.fmt(f),
|
||||
Self::Key(e) => e.fmt(f),
|
||||
Self::Value(e) => e.fmt(f),
|
||||
Self::Value(e) => match std::str::from_utf8(e) {
|
||||
Ok(e) => e.fmt(f),
|
||||
Err(_) => write!(f, "{:02x?}", e),
|
||||
},
|
||||
Self::Newline(e) => e.fmt(f),
|
||||
Self::ValueNotDone(e) => e.fmt(f),
|
||||
Self::ValueDone(e) => e.fmt(f),
|
||||
Self::ValueNotDone(e) => match std::str::from_utf8(e) {
|
||||
Ok(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::KeyValueSeparator => write!(f, "="),
|
||||
}
|
||||
|
@ -111,18 +122,22 @@ impl<'a> Into<Event<'a>> for ParsedSectionHeader<'a> {
|
|||
}
|
||||
|
||||
/// 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)]
|
||||
pub struct ParsedSectionHeader<'a> {
|
||||
/// The name of the header.
|
||||
pub name: Cow<'a, BStr>,
|
||||
pub name: Cow<'a, str>,
|
||||
/// The separator used to determine if the section contains a subsection.
|
||||
/// This is either a period `.` or a string of whitespace. Note that
|
||||
/// reconstruction of subsection format is dependent on this value. If this
|
||||
/// is all whitespace, then the subsection name needs to be surrounded by
|
||||
/// quotes to have perfect reconstruction.
|
||||
pub separator: Option<Cow<'a, BStr>>,
|
||||
pub separator: Option<Cow<'a, str>>,
|
||||
/// The subsection name without quotes if any exist.
|
||||
pub subsection_name: Option<Cow<'a, BStr>>,
|
||||
pub subsection_name: Option<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
impl Display for ParsedSectionHeader<'_> {
|
||||
|
@ -130,9 +145,10 @@ impl Display for ParsedSectionHeader<'_> {
|
|||
write!(f, "[{}", self.name)?;
|
||||
|
||||
if let Some(v) = &self.separator {
|
||||
// Separator must be utf-8
|
||||
v.fmt(f)?;
|
||||
let subsection_name = self.subsection_name.as_ref().unwrap();
|
||||
if *v == b".".as_bstr() {
|
||||
if v == "." {
|
||||
subsection_name.fmt(f)?;
|
||||
} else {
|
||||
write!(f, "\"{}\"", subsection_name)?;
|
||||
|
@ -149,13 +165,20 @@ pub struct ParsedComment<'a> {
|
|||
/// The comment marker used. This is either a semicolon or octothorpe.
|
||||
pub comment_tag: char,
|
||||
/// The parsed comment.
|
||||
pub comment: Cow<'a, BStr>,
|
||||
pub comment: Cow<'a, [u8]>,
|
||||
}
|
||||
|
||||
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 {
|
||||
self.comment_tag.fmt(f)?;
|
||||
self.comment.fmt(f)
|
||||
if let Ok(s) = std::str::from_utf8(&self.comment) {
|
||||
s.fmt(f)
|
||||
} else {
|
||||
write!(f, "{:02x?}", self.comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,7 +258,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 abstraction to a `git-config`
|
||||
/// around the parser to provide a higher-level a[u8]action to a `git-config`
|
||||
/// file, including querying, modifying, and updating values.
|
||||
///
|
||||
/// This parser guarantees that the events emitted are sufficient to
|
||||
|
@ -308,20 +331,20 @@ impl Display for ParserNode {
|
|||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("core".into()),
|
||||
/// # name: Cow::Borrowed("core"),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
/// # let section_data = "[core]\n autocrlf = input";
|
||||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n".into())),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ".into())),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf".into())),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ".into())),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::Whitespace(Cow::Borrowed(" ".into())),
|
||||
/// Event::Value(Cow::Borrowed("input".into())),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
/// Event::Value(Cow::Borrowed(b"input")),
|
||||
/// # ]);
|
||||
/// ```
|
||||
///
|
||||
|
@ -346,19 +369,18 @@ impl Display for ParserNode {
|
|||
/// ```
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # use bstr::BStr;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("core".into()),
|
||||
/// # name: Cow::Borrowed("core"),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
/// # let section_data = "[core]\n autocrlf";
|
||||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n".into())),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ".into())),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf".into())),
|
||||
/// Event::Value(Cow::Borrowed("".into())),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
||||
/// Event::Value(Cow::Borrowed(b"")),
|
||||
/// # ]);
|
||||
/// ```
|
||||
///
|
||||
|
@ -382,21 +404,21 @@ impl Display for ParserNode {
|
|||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("core".into()),
|
||||
/// # name: Cow::Borrowed("core"),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
/// # let section_data = "[core]\nautocrlf=true\"\"\nfilemode=fa\"lse\"";
|
||||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n".into())),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf".into())),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::Value(Cow::Borrowed(r#"true"""#.into())),
|
||||
/// Event::Newline(Cow::Borrowed("\n".into())),
|
||||
/// Event::Key(Cow::Borrowed("filemode".into())),
|
||||
/// Event::Value(Cow::Borrowed(br#"true"""#)),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Key(Cow::Borrowed("filemode")),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::Value(Cow::Borrowed(r#"fa"lse""#.into())),
|
||||
/// Event::Value(Cow::Borrowed(br#"fa"lse""#)),
|
||||
/// # ]);
|
||||
/// ```
|
||||
///
|
||||
|
@ -419,19 +441,19 @@ impl Display for ParserNode {
|
|||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("some-section".into()),
|
||||
/// # name: Cow::Borrowed("some-section"),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
/// # let section_data = "[some-section]\nfile=a\\\n c";
|
||||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n".into())),
|
||||
/// Event::Key(Cow::Borrowed("file".into())),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Key(Cow::Borrowed("file")),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::ValueNotDone(Cow::Borrowed("a".into())),
|
||||
/// Event::Newline(Cow::Borrowed("\n".into())),
|
||||
/// Event::ValueDone(Cow::Borrowed(" c".into())),
|
||||
/// Event::ValueNotDone(Cow::Borrowed(b"a")),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::ValueDone(Cow::Borrowed(b" c")),
|
||||
/// # ]);
|
||||
/// ```
|
||||
///
|
||||
|
@ -487,6 +509,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
// Can't impl IntoIter without allocating.and using a generic associated type
|
||||
// TODO: try harder?
|
||||
|
@ -548,13 +571,13 @@ pub fn parse_from_bytes(input: &[u8]) -> Result<Parser<'_>, ParserError> {
|
|||
let (i, frontmatter) = many0(alt((
|
||||
map(comment, Event::Comment),
|
||||
map(take_spaces, |whitespace| {
|
||||
Event::Whitespace(Cow::Borrowed(whitespace.into()))
|
||||
Event::Whitespace(Cow::Borrowed(whitespace))
|
||||
}),
|
||||
map(take_newline, |(newline, counter)| {
|
||||
newlines += counter;
|
||||
Event::Newline(Cow::Borrowed(newline.into()))
|
||||
Event::Newline(Cow::Borrowed(newline))
|
||||
}),
|
||||
)))(input.as_bytes())
|
||||
)))(input)
|
||||
// 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
|
||||
// if one of its children succeed. However, all of it's children are
|
||||
|
@ -609,7 +632,7 @@ fn comment(i: &[u8]) -> IResult<&[u8], ParsedComment> {
|
|||
i,
|
||||
ParsedComment {
|
||||
comment_tag,
|
||||
comment: Cow::Borrowed(comment.into()),
|
||||
comment: Cow::Borrowed(comment),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -631,7 +654,7 @@ fn section<'a, 'b>(
|
|||
if let Ok((new_i, v)) = take_spaces(i) {
|
||||
if old_i != new_i {
|
||||
i = new_i;
|
||||
items.push(Event::Whitespace(Cow::Borrowed(v.into())));
|
||||
items.push(Event::Whitespace(Cow::Borrowed(v)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,7 +662,7 @@ fn section<'a, 'b>(
|
|||
if old_i != new_i {
|
||||
i = new_i;
|
||||
newlines += new_newlines;
|
||||
items.push(Event::Newline(Cow::Borrowed(v.into())));
|
||||
items.push(Event::Newline(Cow::Borrowed(v)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,21 +701,24 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
|
|||
// 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 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) {
|
||||
// Either section does not have a subsection or using deprecated
|
||||
// subsection syntax at this point.
|
||||
let header = match name.rfind(&[b'.']) {
|
||||
let header = match find_legacy_subsection_separator(name) {
|
||||
Some(index) => ParsedSectionHeader {
|
||||
name: Cow::Borrowed(name[..index].into()),
|
||||
separator: name
|
||||
.get(index..index + 1)
|
||||
.map(|slice| Cow::Borrowed(slice.into())),
|
||||
subsection_name: name
|
||||
.get(index + 1..)
|
||||
.map(|slice| Cow::Borrowed(slice.into())),
|
||||
name: Cow::Borrowed(&name[..index]),
|
||||
separator: name.get(index..index + 1).map(|slice| Cow::Borrowed(slice)),
|
||||
subsection_name: name.get(index + 1..).map(|slice| Cow::Borrowed(slice)),
|
||||
},
|
||||
None => ParsedSectionHeader {
|
||||
name: Cow::Borrowed(name.into()),
|
||||
name: Cow::Borrowed(name),
|
||||
separator: None,
|
||||
subsection_name: None,
|
||||
},
|
||||
|
@ -710,20 +736,38 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
|
|||
tag("\"]"),
|
||||
)(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((
|
||||
i,
|
||||
ParsedSectionHeader {
|
||||
name: Cow::Borrowed(name.into()),
|
||||
separator: Some(Cow::Borrowed(whitespace.into())),
|
||||
name: Cow::Borrowed(name),
|
||||
separator: Some(Cow::Borrowed(whitespace)),
|
||||
// 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.
|
||||
subsection_name: subsection_name
|
||||
.or(Some(b""))
|
||||
.map(|slice| Cow::Borrowed(slice.into())),
|
||||
subsection_name: subsection_name.or(Some("")).map(Cow::Borrowed),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
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>(
|
||||
i: &'a [u8],
|
||||
node: &'b mut ParserNode,
|
||||
|
@ -733,12 +777,12 @@ fn section_body<'a, 'b, 'c>(
|
|||
*node = ParserNode::ConfigName;
|
||||
let (i, name) = config_name(i)?;
|
||||
|
||||
items.push(Event::Key(Cow::Borrowed(name.into())));
|
||||
items.push(Event::Key(Cow::Borrowed(name)));
|
||||
|
||||
let (i, whitespace) = opt(take_spaces)(i)?;
|
||||
|
||||
if let Some(whitespace) = whitespace {
|
||||
items.push(Event::Whitespace(Cow::Borrowed(whitespace.into())));
|
||||
items.push(Event::Whitespace(Cow::Borrowed(whitespace)));
|
||||
}
|
||||
|
||||
let (i, _) = config_value(i, items)?;
|
||||
|
@ -747,7 +791,7 @@ fn section_body<'a, 'b, 'c>(
|
|||
|
||||
/// Parses the config name of a config pair. Assumes the input has already been
|
||||
/// trimmed of any leading whitespace.
|
||||
fn config_name(i: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
fn config_name(i: &[u8]) -> IResult<&[u8], &str> {
|
||||
if i.is_empty() {
|
||||
return Err(nom::Err::Error(NomError {
|
||||
input: i,
|
||||
|
@ -761,7 +805,16 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
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], ()> {
|
||||
|
@ -769,12 +822,12 @@ fn config_value<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<
|
|||
events.push(Event::KeyValueSeparator);
|
||||
let (i, whitespace) = opt(take_spaces)(i)?;
|
||||
if let Some(whitespace) = whitespace {
|
||||
events.push(Event::Whitespace(Cow::Borrowed(whitespace.into())));
|
||||
events.push(Event::Whitespace(Cow::Borrowed(whitespace)));
|
||||
}
|
||||
let (i, _) = value_impl(i, events)?;
|
||||
Ok((i, ()))
|
||||
} else {
|
||||
events.push(Event::Value(Cow::Borrowed("".into())));
|
||||
events.push(Event::Value(Cow::Borrowed(b"")));
|
||||
Ok((i, ()))
|
||||
}
|
||||
}
|
||||
|
@ -804,10 +857,10 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<&'
|
|||
// continuation.
|
||||
b'\n' => {
|
||||
partial_value_found = true;
|
||||
events.push(Event::ValueNotDone(Cow::Borrowed(
|
||||
i[offset..index - 1].into(),
|
||||
events.push(Event::ValueNotDone(Cow::Borrowed(&i[offset..index - 1])));
|
||||
events.push(Event::Newline(Cow::Borrowed(
|
||||
std::str::from_utf8(&i[index..index + 1]).unwrap(),
|
||||
)));
|
||||
events.push(Event::Newline(Cow::Borrowed(i[index..index + 1].into())));
|
||||
offset = index + 1;
|
||||
parsed_index = 0;
|
||||
}
|
||||
|
@ -868,15 +921,15 @@ fn value_impl<'a, 'b>(i: &'a [u8], events: &'b mut Vec<Event<'a>>) -> IResult<&'
|
|||
};
|
||||
|
||||
if partial_value_found {
|
||||
events.push(Event::ValueDone(Cow::Borrowed(remainder_value.into())));
|
||||
events.push(Event::ValueDone(Cow::Borrowed(remainder_value)));
|
||||
} else {
|
||||
events.push(Event::Value(Cow::Borrowed(remainder_value.into())));
|
||||
events.push(Event::Value(Cow::Borrowed(remainder_value)));
|
||||
}
|
||||
|
||||
Ok((i, ()))
|
||||
}
|
||||
|
||||
fn take_spaces(i: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||
fn take_spaces(i: &[u8]) -> IResult<&[u8], &str> {
|
||||
let (i, v) = take_while(|c| (c as char).is_ascii() && is_space(c))(i)?;
|
||||
if v.is_empty() {
|
||||
Err(nom::Err::Error(NomError {
|
||||
|
@ -884,11 +937,12 @@ fn take_spaces(i: &[u8]) -> IResult<&[u8], &[u8]> {
|
|||
code: ErrorKind::Eof,
|
||||
}))
|
||||
} else {
|
||||
Ok((i, v))
|
||||
// v is guaranteed to be utf-8
|
||||
Ok((i, std::str::from_utf8(v).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
fn take_newline(i: &[u8]) -> IResult<&[u8], (&[u8], usize)> {
|
||||
fn take_newline(i: &[u8]) -> IResult<&[u8], (&str, usize)> {
|
||||
let mut counter = 0;
|
||||
let (i, v) = take_while(|c| (c as char).is_ascii() && is_newline(c))(i)?;
|
||||
counter += v.len();
|
||||
|
@ -898,7 +952,8 @@ fn take_newline(i: &[u8]) -> IResult<&[u8], (&[u8], usize)> {
|
|||
code: ErrorKind::Eof,
|
||||
}))
|
||||
} else {
|
||||
Ok((i, (v, counter)))
|
||||
// v is guaranteed to be utf-8
|
||||
Ok((i, (std::str::from_utf8(v).unwrap(), counter)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1011,10 +1066,7 @@ mod config_name {
|
|||
|
||||
#[test]
|
||||
fn just_name() {
|
||||
assert_eq!(
|
||||
config_name(b"name").unwrap(),
|
||||
fully_consumed("name".as_bytes())
|
||||
);
|
||||
assert_eq!(config_name(b"name").unwrap(), fully_consumed("name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -13,12 +13,12 @@ pub fn section_header(
|
|||
name: &str,
|
||||
subsection: impl Into<Option<(&'static str, &'static str)>>,
|
||||
) -> ParsedSectionHeader<'_> {
|
||||
let name = Cow::Borrowed(name.into());
|
||||
let name = name.into();
|
||||
if let Some((separator, subsection_name)) = subsection.into() {
|
||||
ParsedSectionHeader {
|
||||
name,
|
||||
separator: Some(Cow::Borrowed(separator.into())),
|
||||
subsection_name: Some(Cow::Borrowed(subsection_name.into())),
|
||||
separator: Some(Cow::Borrowed(separator)),
|
||||
subsection_name: Some(Cow::Borrowed(subsection_name)),
|
||||
}
|
||||
} else {
|
||||
ParsedSectionHeader {
|
||||
|
@ -30,19 +30,19 @@ pub fn section_header(
|
|||
}
|
||||
|
||||
pub(crate) fn name_event(name: &'static str) -> Event<'static> {
|
||||
Event::Key(Cow::Borrowed(name.into()))
|
||||
Event::Key(Cow::Borrowed(name))
|
||||
}
|
||||
|
||||
pub(crate) fn value_event(value: &'static str) -> Event<'static> {
|
||||
Event::Value(Cow::Borrowed(value.into()))
|
||||
Event::Value(Cow::Borrowed(value.as_bytes()))
|
||||
}
|
||||
|
||||
pub(crate) fn value_not_done_event(value: &'static str) -> Event<'static> {
|
||||
Event::ValueNotDone(Cow::Borrowed(value.into()))
|
||||
Event::ValueNotDone(Cow::Borrowed(value.as_bytes()))
|
||||
}
|
||||
|
||||
pub(crate) fn value_done_event(value: &'static str) -> Event<'static> {
|
||||
Event::ValueDone(Cow::Borrowed(value.into()))
|
||||
Event::ValueDone(Cow::Borrowed(value.as_bytes()))
|
||||
}
|
||||
|
||||
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> {
|
||||
Event::Newline(Cow::Borrowed(value.into()))
|
||||
Event::Newline(Cow::Borrowed(value))
|
||||
}
|
||||
|
||||
pub(crate) fn whitespace_event(value: &'static str) -> Event<'static> {
|
||||
Event::Whitespace(Cow::Borrowed(value.into()))
|
||||
Event::Whitespace(Cow::Borrowed(value))
|
||||
}
|
||||
|
||||
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> {
|
||||
ParsedComment {
|
||||
comment_tag,
|
||||
comment: Cow::Borrowed(comment.into()),
|
||||
comment: Cow::Borrowed(comment.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//! Rust containers for valid `git-config` types.
|
||||
|
||||
use bstr::{BStr, ByteSlice};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::borrow::Cow;
|
||||
|
@ -121,7 +120,7 @@ pub enum Value<'a> {
|
|||
/// 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
|
||||
/// will never fail.
|
||||
Other(Cow<'a, BStr>),
|
||||
Other(Cow<'a, [u8]>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Value<'a> {
|
||||
|
@ -138,7 +137,7 @@ impl<'a> From<&'a str> for Value<'a> {
|
|||
return Self::Color(color);
|
||||
}
|
||||
|
||||
Self::Other(Cow::Borrowed(s.into()))
|
||||
Self::Other(Cow::Borrowed(s.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,7 +147,7 @@ impl<'a> From<&'a [u8]> for Value<'a> {
|
|||
if let Ok(s) = std::str::from_utf8(s) {
|
||||
Self::from(s)
|
||||
} else {
|
||||
Self::Other(Cow::Borrowed(s.as_bstr()))
|
||||
Self::Other(Cow::Borrowed(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,9 +174,7 @@ impl Serialize for Value<'_> {
|
|||
/// 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 [`BStr`]s.
|
||||
///
|
||||
/// [`BStr`]: bstr::BStr
|
||||
/// Consequently, variants hold [`str`]s rather than [`[u8]`]s.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Boolean<'a> {
|
||||
|
|
|
@ -34,7 +34,7 @@ fn name(name: &'static str) -> Event<'static> {
|
|||
}
|
||||
|
||||
fn value(value: &'static str) -> Event<'static> {
|
||||
Event::Value(Cow::Borrowed(value.into()))
|
||||
Event::Value(Cow::Borrowed(value.as_bytes()))
|
||||
}
|
||||
|
||||
fn newline() -> Event<'static> {
|
||||
|
|
Reference in a new issue