implement case insensitivity for names
This commit is contained in:
parent
6167914277
commit
9715790574
8 changed files with 240 additions and 82 deletions
1
LICENSE-APACHE
Symbolic link
1
LICENSE-APACHE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../LICENSE-APACHE
|
1
LICENSE-MIT
Symbolic link
1
LICENSE-MIT
Symbolic link
|
@ -0,0 +1 @@
|
|||
../LICENSE-MIT
|
18
README.md
Normal file
18
README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# git-config
|
||||
|
||||
**git-config is a library for interacting with `git-config` files.**
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in git-config by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
||||
</sub>
|
149
src/file.rs
149
src/file.rs
|
@ -1,23 +1,25 @@
|
|||
//! This module provides a high level wrapper around a single `git-config` file.
|
||||
|
||||
use crate::parser::{parse_from_bytes, Error, Event, ParsedSectionHeader, Parser};
|
||||
use crate::values::{normalize_bytes, normalize_cow, normalize_vec};
|
||||
use std::{borrow::Borrow, convert::TryFrom, ops::Deref};
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
ops::DerefMut,
|
||||
use crate::parser::{
|
||||
parse_from_bytes, Error, Event, Key, ParsedSectionHeader, Parser, SectionHeaderName,
|
||||
};
|
||||
use crate::values::{normalize_bytes, normalize_cow, normalize_vec};
|
||||
use std::borrow::Borrow;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// All possible error types that may occur from interacting with [`GitConfig`].
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
|
||||
#[derive(PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Debug)]
|
||||
pub enum GitConfigError<'a> {
|
||||
/// The requested section does not exist.
|
||||
SectionDoesNotExist(&'a str),
|
||||
SectionDoesNotExist(SectionHeaderName<'a>),
|
||||
/// The requested subsection does not exist.
|
||||
SubSectionDoesNotExist(Option<&'a str>),
|
||||
/// The key does not exist in the requested section.
|
||||
KeyDoesNotExist(&'a str),
|
||||
KeyDoesNotExist(Key<'a>),
|
||||
/// The conversion into the provided type for methods such as
|
||||
/// [`GitConfig::get_value`] failed.
|
||||
FailedConversion,
|
||||
|
@ -68,7 +70,7 @@ enum LookupTreeNode<'a> {
|
|||
/// time.
|
||||
pub struct MutableValue<'borrow, 'lookup, 'event> {
|
||||
section: &'borrow mut Vec<Event<'event>>,
|
||||
key: &'lookup str,
|
||||
key: Key<'lookup>,
|
||||
index: usize,
|
||||
size: usize,
|
||||
}
|
||||
|
@ -109,7 +111,7 @@ impl MutableValue<'_, '_, '_> {
|
|||
latest_value
|
||||
.map(normalize_cow)
|
||||
.or_else(|| partial_value.map(normalize_vec))
|
||||
.ok_or(GitConfigError::KeyDoesNotExist(self.key))
|
||||
.ok_or(GitConfigError::KeyDoesNotExist(self.key.to_owned()))
|
||||
}
|
||||
|
||||
/// Update the value to the provided one. This modifies the value such that
|
||||
|
@ -131,8 +133,10 @@ impl MutableValue<'_, '_, '_> {
|
|||
self.section
|
||||
.insert(self.index, Event::Value(Cow::Owned(input)));
|
||||
self.section.insert(self.index, Event::KeyValueSeparator);
|
||||
self.section
|
||||
.insert(self.index, Event::Key(Cow::Owned(self.key.into())));
|
||||
self.section.insert(
|
||||
self.index,
|
||||
Event::Key(Key(Cow::Owned(self.key.0.to_string()))),
|
||||
);
|
||||
}
|
||||
|
||||
/// Removes the value. Does nothing when called multiple times in
|
||||
|
@ -193,7 +197,7 @@ impl DerefMut for Offset {
|
|||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub struct MutableMultiValue<'borrow, 'lookup, 'event> {
|
||||
section: &'borrow mut HashMap<SectionId, Vec<Event<'event>>>,
|
||||
key: &'lookup str,
|
||||
key: Key<'lookup>,
|
||||
/// Each entry data struct provides sufficient information to index into
|
||||
/// [`Self::offsets`]. This layer of indirection is used for users to index
|
||||
/// into the offsets rather than leaking the internal data structures.
|
||||
|
@ -244,7 +248,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
|||
}
|
||||
|
||||
if values.is_empty() {
|
||||
return Err(GitConfigError::KeyDoesNotExist(self.key));
|
||||
return Err(GitConfigError::KeyDoesNotExist(self.key.to_owned()));
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
|
@ -296,7 +300,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
|||
offset_index,
|
||||
} = self.indices_and_sizes[index];
|
||||
MutableMultiValue::set_value_inner(
|
||||
self.key,
|
||||
self.key.to_owned(),
|
||||
&mut self.offsets,
|
||||
self.section.get_mut(§ion_id).unwrap(),
|
||||
section_id,
|
||||
|
@ -323,7 +327,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
|||
) in self.indices_and_sizes.iter().zip(input)
|
||||
{
|
||||
Self::set_value_inner(
|
||||
self.key,
|
||||
self.key.to_owned(),
|
||||
&mut self.offsets,
|
||||
self.section.get_mut(section_id).unwrap(),
|
||||
*section_id,
|
||||
|
@ -350,7 +354,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
|||
} in &self.indices_and_sizes
|
||||
{
|
||||
Self::set_value_inner(
|
||||
self.key,
|
||||
self.key.to_owned(),
|
||||
&mut self.offsets,
|
||||
self.section.get_mut(section_id).unwrap(),
|
||||
*section_id,
|
||||
|
@ -373,7 +377,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
|||
} in &self.indices_and_sizes
|
||||
{
|
||||
Self::set_value_inner(
|
||||
self.key,
|
||||
self.key.to_owned(),
|
||||
&mut self.offsets,
|
||||
self.section.get_mut(section_id).unwrap(),
|
||||
*section_id,
|
||||
|
@ -384,7 +388,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
|||
}
|
||||
|
||||
fn set_value_inner<'a: 'event>(
|
||||
key: &'lookup str,
|
||||
key: Key<'lookup>,
|
||||
offsets: &mut HashMap<SectionId, Vec<Offset>>,
|
||||
section: &mut Vec<Event<'event>>,
|
||||
section_id: SectionId,
|
||||
|
@ -398,7 +402,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
|
|||
MutableMultiValue::set_offset(offsets, section_id, offset_index, 3);
|
||||
section.insert(offset, Event::Value(input));
|
||||
section.insert(offset, Event::KeyValueSeparator);
|
||||
section.insert(offset, Event::Key(Cow::Owned(key.into())));
|
||||
section.insert(offset, Event::Key(Key(Cow::Owned(key.0.to_string()))));
|
||||
}
|
||||
|
||||
/// Removes the value at the given index. Does nothing when called multiple
|
||||
|
@ -530,7 +534,7 @@ pub struct GitConfig<'a> {
|
|||
/// `git-config` file prohibits global values, this vec is limited to only
|
||||
/// comment, newline, and whitespace events.
|
||||
frontmatter_events: Vec<Event<'a>>,
|
||||
section_lookup_tree: HashMap<Cow<'a, str>, Vec<LookupTreeNode<'a>>>,
|
||||
section_lookup_tree: HashMap<SectionHeaderName<'a>, Vec<LookupTreeNode<'a>>>,
|
||||
/// SectionId to section mapping. The value of this HashMap contains actual
|
||||
/// events.
|
||||
///
|
||||
|
@ -675,6 +679,7 @@ impl<'event> GitConfig<'event> {
|
|||
// the "last one wins" resolution strategy by `git-config`).
|
||||
let section_ids =
|
||||
self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
|
||||
let key = Key(key.into());
|
||||
|
||||
for section_id in section_ids.iter().rev() {
|
||||
let mut found_key = false;
|
||||
|
@ -728,6 +733,7 @@ impl<'event> GitConfig<'event> {
|
|||
) -> Result<MutableValue<'_, 'lookup, 'event>, GitConfigError<'lookup>> {
|
||||
let section_ids =
|
||||
self.get_section_ids_by_name_and_subname(section_name, subsection_name)?;
|
||||
let key = Key(key.into());
|
||||
|
||||
for section_id in section_ids.iter().rev() {
|
||||
let mut size = 0;
|
||||
|
@ -814,7 +820,7 @@ impl<'event> GitConfig<'event> {
|
|||
subsection_name: Option<&'lookup str>,
|
||||
key: &'lookup str,
|
||||
) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> {
|
||||
let key = key;
|
||||
let key = Key(key.into());
|
||||
let mut values = vec![];
|
||||
for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? {
|
||||
let mut found_key = false;
|
||||
|
@ -912,6 +918,7 @@ impl<'event> GitConfig<'event> {
|
|||
let section_ids = self
|
||||
.get_section_ids_by_name_and_subname(section_name, subsection_name)?
|
||||
.to_vec();
|
||||
let key = Key(key.into());
|
||||
|
||||
let mut offsets = HashMap::new();
|
||||
let mut entries = vec![];
|
||||
|
@ -1103,26 +1110,23 @@ impl<'event> GitConfig<'event> {
|
|||
subsection_name: impl Into<Option<Cow<'event, str>>>,
|
||||
) {
|
||||
self.push_section(
|
||||
Some(section_name.into()),
|
||||
Some(SectionHeaderName(section_name.into())),
|
||||
subsection_name.into(),
|
||||
&mut Some(vec![]),
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes the section, returning the events it had, if any.
|
||||
pub fn remove_section(
|
||||
pub fn remove_section<'lookup>(
|
||||
&mut self,
|
||||
section_name: impl Into<Cow<'event, str>>,
|
||||
subsection_name: impl Into<Option<Cow<'event, str>>>,
|
||||
section_name: &'lookup str,
|
||||
subsection_name: impl Into<Option<&'lookup str>>,
|
||||
) -> Option<Vec<Event>> {
|
||||
let mut section_ids = self
|
||||
.get_section_ids_by_name_and_subname(
|
||||
§ion_name.into(),
|
||||
subsection_name.into().as_deref(),
|
||||
)
|
||||
.ok()?;
|
||||
let section_ids =
|
||||
self.get_section_ids_by_name_and_subname(section_name, subsection_name.into());
|
||||
let section_ids = section_ids.ok()?.pop()?;
|
||||
|
||||
self.sections.remove(§ion_ids.pop()?)
|
||||
self.sections.remove(§ion_ids)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1131,7 +1135,7 @@ impl<'event> GitConfig<'event> {
|
|||
/// Used during initialization.
|
||||
fn push_section(
|
||||
&mut self,
|
||||
current_section_name: Option<Cow<'event, str>>,
|
||||
current_section_name: Option<SectionHeaderName<'event>>,
|
||||
current_subsection_name: Option<Cow<'event, str>>,
|
||||
maybe_section: &mut Option<Vec<Event<'event>>>,
|
||||
) {
|
||||
|
@ -1184,21 +1188,22 @@ impl<'event> GitConfig<'event> {
|
|||
/// Returns the mapping between section and subsection name to section ids.
|
||||
fn get_section_ids_by_name_and_subname<'lookup>(
|
||||
&self,
|
||||
section_name: &'lookup str,
|
||||
section_name: impl Into<SectionHeaderName<'lookup>>,
|
||||
subsection_name: Option<&'lookup str>,
|
||||
) -> Result<Vec<SectionId>, GitConfigError<'lookup>> {
|
||||
let section_name = section_name.into();
|
||||
let section_ids = self
|
||||
.section_lookup_tree
|
||||
.get(section_name)
|
||||
.get(§ion_name)
|
||||
.ok_or(GitConfigError::SectionDoesNotExist(section_name))?;
|
||||
let mut maybe_ids = None;
|
||||
// Don't simplify if and matches here -- the for loop currently needs
|
||||
// `n + 1` checks, while the if and matches will result in the for loop
|
||||
// needing `2n` checks.
|
||||
if let Some(subsect_name) = subsection_name {
|
||||
if let Some(subsection_name) = subsection_name {
|
||||
for node in section_ids {
|
||||
if let LookupTreeNode::NonTerminal(subsection_lookup) = node {
|
||||
maybe_ids = subsection_lookup.get(subsect_name);
|
||||
maybe_ids = subsection_lookup.get(subsection_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1252,7 +1257,7 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
|
|||
};
|
||||
|
||||
// Current section that we're building
|
||||
let mut current_section_name: Option<Cow<'a, str>> = None;
|
||||
let mut current_section_name: Option<SectionHeaderName<'_>> = None;
|
||||
let mut current_subsection_name: Option<Cow<'a, str>> = None;
|
||||
let mut maybe_section: Option<Vec<Event<'a>>> = None;
|
||||
|
||||
|
@ -1613,16 +1618,48 @@ mod mutable_multi_value {
|
|||
.get_raw_multi_value_mut("core", None, "a")
|
||||
.unwrap();
|
||||
values.delete_all();
|
||||
assert!(values.get().is_err());
|
||||
assert_eq!(
|
||||
git_config.to_string(),
|
||||
"[core]\n \n [core]\n \n ",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_values_are_supported() {
|
||||
let mut git_config = GitConfig::try_from(
|
||||
r#"[core]
|
||||
a=b\
|
||||
"100"
|
||||
[core]
|
||||
a=d\
|
||||
b
|
||||
a=f\
|
||||
a"#,
|
||||
)
|
||||
.unwrap();
|
||||
let mut values = git_config
|
||||
.get_raw_multi_value_mut("core", None, "a")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&*values.get().unwrap(),
|
||||
vec![
|
||||
Cow::<[u8]>::Owned(b"b100".to_vec()),
|
||||
Cow::<[u8]>::Borrowed(b"db"),
|
||||
Cow::<[u8]>::Borrowed(b"fa"),
|
||||
]
|
||||
);
|
||||
|
||||
values.delete_all();
|
||||
assert!(values.get().is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod from_parser {
|
||||
use super::{Cow, Event, GitConfig, HashMap, LookupTreeNode, SectionId, TryFrom};
|
||||
use crate::parser::SectionHeaderName;
|
||||
use crate::test_util::{name_event, newline_event, section_header, value_event};
|
||||
|
||||
#[test]
|
||||
|
@ -1648,7 +1685,7 @@ mod from_parser {
|
|||
let expected_lookup_tree = {
|
||||
let mut tree = HashMap::new();
|
||||
tree.insert(
|
||||
Cow::Borrowed("core"),
|
||||
SectionHeaderName(Cow::Borrowed("core")),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
||||
);
|
||||
tree
|
||||
|
@ -1677,10 +1714,10 @@ mod from_parser {
|
|||
|
||||
#[test]
|
||||
fn parse_single_subsection() {
|
||||
let mut config = GitConfig::try_from("[core.subsec]\na=b\nc=d").unwrap();
|
||||
let mut config = GitConfig::try_from("[core.sub]\na=b\nc=d").unwrap();
|
||||
let expected_separators = {
|
||||
let mut map = HashMap::new();
|
||||
map.insert(SectionId(0), section_header("core", (".", "subsec")));
|
||||
map.insert(SectionId(0), section_header("core", (".", "sub")));
|
||||
map
|
||||
};
|
||||
assert_eq!(config.section_headers, expected_separators);
|
||||
|
@ -1688,9 +1725,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"), vec![SectionId(0)]);
|
||||
inner_tree.insert(Cow::Borrowed("sub"), vec![SectionId(0)]);
|
||||
tree.insert(
|
||||
Cow::Borrowed("core"),
|
||||
SectionHeaderName(Cow::Borrowed("core")),
|
||||
vec![LookupTreeNode::NonTerminal(inner_tree)],
|
||||
);
|
||||
tree
|
||||
|
@ -1731,11 +1768,11 @@ mod from_parser {
|
|||
let expected_lookup_tree = {
|
||||
let mut tree = HashMap::new();
|
||||
tree.insert(
|
||||
Cow::Borrowed("core"),
|
||||
SectionHeaderName(Cow::Borrowed("core")),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(0)])],
|
||||
);
|
||||
tree.insert(
|
||||
Cow::Borrowed("other"),
|
||||
SectionHeaderName(Cow::Borrowed("other")),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(1)])],
|
||||
);
|
||||
tree
|
||||
|
@ -1784,7 +1821,7 @@ mod from_parser {
|
|||
let expected_lookup_tree = {
|
||||
let mut tree = HashMap::new();
|
||||
tree.insert(
|
||||
Cow::Borrowed("core"),
|
||||
SectionHeaderName(Cow::Borrowed("core")),
|
||||
vec![LookupTreeNode::Terminal(vec![SectionId(0), SectionId(1)])],
|
||||
);
|
||||
tree
|
||||
|
@ -1823,6 +1860,7 @@ mod from_parser {
|
|||
#[cfg(test)]
|
||||
mod get_raw_value {
|
||||
use super::{Cow, GitConfig, GitConfigError, TryFrom};
|
||||
use crate::parser::{Key, SectionHeaderName};
|
||||
|
||||
#[test]
|
||||
fn single_section() {
|
||||
|
@ -1860,7 +1898,9 @@ 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"))
|
||||
Err(GitConfigError::SectionDoesNotExist(SectionHeaderName(
|
||||
"foo".into()
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1878,7 +1918,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"))
|
||||
Err(GitConfigError::KeyDoesNotExist(Key("aaaaaa".into())))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1918,6 +1958,7 @@ mod get_value {
|
|||
#[cfg(test)]
|
||||
mod get_raw_multi_value {
|
||||
use super::{Cow, GitConfig, GitConfigError, TryFrom};
|
||||
use crate::parser::{Key, SectionHeaderName};
|
||||
|
||||
#[test]
|
||||
fn single_value_is_identical_to_single_value_query() {
|
||||
|
@ -1955,7 +1996,9 @@ 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"))
|
||||
Err(GitConfigError::SectionDoesNotExist(SectionHeaderName(
|
||||
"foo".into()
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1973,7 +2016,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"))
|
||||
Err(GitConfigError::KeyDoesNotExist(Key("aaaaaa".into())))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
112
src/parser.rs
112
src/parser.rs
|
@ -18,9 +18,10 @@ use nom::error::{Error as NomError, ErrorKind};
|
|||
use nom::multi::{many0, many1};
|
||||
use nom::sequence::delimited;
|
||||
use nom::IResult;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
use std::iter::FusedIterator;
|
||||
use std::{convert::TryFrom, fmt::Display};
|
||||
use std::{borrow::Cow, hash::Hash};
|
||||
|
||||
/// Syntactic events that occurs in the config. Despite all these variants
|
||||
/// holding a [`Cow`] instead over a simple reference, the parser will only emit
|
||||
|
@ -42,7 +43,7 @@ pub enum Event<'a> {
|
|||
/// exists.
|
||||
SectionHeader(ParsedSectionHeader<'a>),
|
||||
/// A name to a value in a section.
|
||||
Key(Cow<'a, str>),
|
||||
Key(Key<'a>),
|
||||
/// 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
|
||||
|
@ -92,7 +93,8 @@ impl Display for Event<'_> {
|
|||
}
|
||||
Self::Comment(e) => e.fmt(f),
|
||||
Self::SectionHeader(e) => e.fmt(f),
|
||||
Self::Key(e) | Self::Newline(e) | Self::Whitespace(e) => e.fmt(f),
|
||||
Self::Key(e) => e.fmt(f),
|
||||
Self::Newline(e) | Self::Whitespace(e) => e.fmt(f),
|
||||
Self::KeyValueSeparator => write!(f, "="),
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +112,8 @@ impl Into<Vec<u8>> for &Event<'_> {
|
|||
Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(),
|
||||
Event::Comment(e) => e.into(),
|
||||
Event::SectionHeader(e) => e.into(),
|
||||
Event::Key(e) | Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(),
|
||||
Event::Key(e) => e.0.as_bytes().to_vec(),
|
||||
Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(),
|
||||
Event::KeyValueSeparator => vec![b'='],
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +146,7 @@ impl Display for ParsedSection<'_> {
|
|||
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
|
||||
pub struct ParsedSectionHeader<'a> {
|
||||
/// The name of the header.
|
||||
pub name: Cow<'a, str>,
|
||||
pub name: SectionHeaderName<'a>,
|
||||
/// 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
|
||||
|
@ -154,6 +157,69 @@ pub struct ParsedSectionHeader<'a> {
|
|||
pub subsection_name: Option<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
macro_rules! generate_case_insensitive {
|
||||
($name:ident, $inner_type:ty, $comment:literal) => {
|
||||
/// Wrapper struct for $comment, since $comment are case-insensitive.
|
||||
#[derive(Clone, Eq, Ord, Debug, Default)]
|
||||
pub struct $name<'a>(pub $inner_type);
|
||||
|
||||
impl PartialEq for $name<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.eq_ignore_ascii_case(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for $name<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for $name<'_> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.0
|
||||
.to_ascii_lowercase()
|
||||
.partial_cmp(&other.0.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for $name<'_> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.to_ascii_lowercase().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for $name<'a> {
|
||||
fn from(s: &'a str) -> Self {
|
||||
Self(Cow::Borrowed(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for $name<'a> {
|
||||
fn from(s: Cow<'a, str>) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::Deref for $name<'a> {
|
||||
type Target = $inner_type;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::DerefMut for $name<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_case_insensitive!(SectionHeaderName, Cow<'a, str>, "section names");
|
||||
generate_case_insensitive!(Key, Cow<'a, str>, "keys");
|
||||
|
||||
impl ParsedSectionHeader<'_> {
|
||||
/// Generates a byte representation of the value. This should be used when
|
||||
/// non-UTF-8 sequences are present or a UTF-8 representation can't be
|
||||
|
@ -386,10 +452,10 @@ impl Display for ParserNode {
|
|||
/// non-significant events that occur in addition to the ones you may expect:
|
||||
///
|
||||
/// ```
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("core"),
|
||||
/// # name: SectionHeaderName(Cow::Borrowed("core")),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
|
@ -398,7 +464,7 @@ impl Display for ParserNode {
|
|||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
||||
/// Event::Key(Key(Cow::Borrowed("autocrlf"))),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
|
@ -425,10 +491,10 @@ impl Display for ParserNode {
|
|||
/// which means that the corresponding event won't appear either:
|
||||
///
|
||||
/// ```
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("core"),
|
||||
/// # name: SectionHeaderName(Cow::Borrowed("core")),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
|
@ -437,7 +503,7 @@ impl Display for ParserNode {
|
|||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Whitespace(Cow::Borrowed(" ")),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
||||
/// Event::Key(Key(Cow::Borrowed("autocrlf"))),
|
||||
/// Event::Value(Cow::Borrowed(b"")),
|
||||
/// # ]);
|
||||
/// ```
|
||||
|
@ -459,10 +525,10 @@ impl Display for ParserNode {
|
|||
/// relevant event stream emitted is thus emitted as:
|
||||
///
|
||||
/// ```
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("core"),
|
||||
/// # name: SectionHeaderName(Cow::Borrowed("core")),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
|
@ -470,11 +536,11 @@ impl Display for ParserNode {
|
|||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Key(Cow::Borrowed("autocrlf")),
|
||||
/// Event::Key(Key(Cow::Borrowed("autocrlf"))),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::Value(Cow::Borrowed(br#"true"""#)),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Key(Cow::Borrowed("filemode")),
|
||||
/// Event::Key(Key(Cow::Borrowed("filemode"))),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::Value(Cow::Borrowed(br#"fa"lse""#)),
|
||||
/// # ]);
|
||||
|
@ -496,10 +562,10 @@ impl Display for ParserNode {
|
|||
/// split value accordingly:
|
||||
///
|
||||
/// ```
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str};
|
||||
/// # use git_config::parser::{Event, ParsedSectionHeader, parse_from_str, SectionHeaderName, Key};
|
||||
/// # use std::borrow::Cow;
|
||||
/// # let section_header = ParsedSectionHeader {
|
||||
/// # name: Cow::Borrowed("some-section"),
|
||||
/// # name: SectionHeaderName(Cow::Borrowed("some-section")),
|
||||
/// # separator: None,
|
||||
/// # subsection_name: None,
|
||||
/// # };
|
||||
|
@ -507,7 +573,7 @@ impl Display for ParserNode {
|
|||
/// # assert_eq!(parse_from_str(section_data).unwrap().into_vec(), vec![
|
||||
/// Event::SectionHeader(section_header),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
/// Event::Key(Cow::Borrowed("file")),
|
||||
/// Event::Key(Key(Cow::Borrowed("file"))),
|
||||
/// Event::KeyValueSeparator,
|
||||
/// Event::ValueNotDone(Cow::Borrowed(b"a")),
|
||||
/// Event::Newline(Cow::Borrowed("\n")),
|
||||
|
@ -772,12 +838,12 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
|
|||
// subsection syntax at this point.
|
||||
let header = match memchr::memrchr(b'.', name.as_bytes()) {
|
||||
Some(index) => ParsedSectionHeader {
|
||||
name: Cow::Borrowed(&name[..index]),
|
||||
name: SectionHeaderName(Cow::Borrowed(&name[..index])),
|
||||
separator: name.get(index..=index).map(|slice| Cow::Borrowed(slice)),
|
||||
subsection_name: name.get(index + 1..).map(|slice| Cow::Borrowed(slice)),
|
||||
},
|
||||
None => ParsedSectionHeader {
|
||||
name: Cow::Borrowed(name),
|
||||
name: SectionHeaderName(Cow::Borrowed(name)),
|
||||
separator: None,
|
||||
subsection_name: None,
|
||||
},
|
||||
|
@ -808,7 +874,7 @@ fn section_header(i: &[u8]) -> IResult<&[u8], ParsedSectionHeader> {
|
|||
Ok((
|
||||
i,
|
||||
ParsedSectionHeader {
|
||||
name: Cow::Borrowed(name),
|
||||
name: SectionHeaderName(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.
|
||||
|
@ -826,7 +892,7 @@ fn section_body<'a, 'b, 'c>(
|
|||
*node = ParserNode::ConfigName;
|
||||
let (i, name) = config_name(i)?;
|
||||
|
||||
items.push(Event::Key(Cow::Borrowed(name)));
|
||||
items.push(Event::Key(Key(Cow::Borrowed(name))));
|
||||
|
||||
let (i, whitespace) = opt(take_spaces)(i)?;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! This module is only included for tests, and contains common unit test helper
|
||||
//! functions.
|
||||
|
||||
use crate::parser::{Event, ParsedComment, ParsedSectionHeader};
|
||||
use crate::parser::{Event, Key, ParsedComment, ParsedSectionHeader};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub fn section_header_event(
|
||||
|
@ -32,7 +32,7 @@ pub fn section_header(
|
|||
}
|
||||
|
||||
pub(crate) fn name_event(name: &'static str) -> Event<'static> {
|
||||
Event::Key(Cow::Borrowed(name))
|
||||
Event::Key(Key(Cow::Borrowed(name)))
|
||||
}
|
||||
|
||||
pub(crate) fn value_event(value: &'static str) -> Event<'static> {
|
||||
|
|
|
@ -97,3 +97,33 @@ fn get_value_looks_up_all_sections_before_failing() -> Result<(), Box<dyn std::e
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn section_names_are_case_insensitive() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = "[core] bool-implicit";
|
||||
let file = GitConfig::try_from(config)?;
|
||||
assert!(file
|
||||
.get_value::<Boolean>("core", None, "bool-implicit")
|
||||
.is_ok());
|
||||
assert_eq!(
|
||||
file.get_value::<Boolean>("core", None, "bool-implicit"),
|
||||
file.get_value::<Boolean>("CORE", None, "bool-implicit")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_names_are_case_insensitive() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = "[core]
|
||||
a = true
|
||||
A = false";
|
||||
let file = GitConfig::try_from(config)?;
|
||||
assert_eq!(file.get_multi_value::<Boolean>("core", None, "a")?.len(), 2);
|
||||
assert_eq!(
|
||||
file.get_value::<Boolean>("core", None, "a"),
|
||||
file.get_value::<Boolean>("core", None, "A")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use git_config::parser::{parse_from_str, Event, Key, ParsedSectionHeader, SectionHeaderName};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use git_config::parser::{parse_from_str, Event, ParsedSectionHeader};
|
||||
|
||||
pub fn section_header_event(
|
||||
name: &str,
|
||||
subsection: impl Into<Option<(&'static str, &'static str)>>,
|
||||
|
@ -13,7 +12,7 @@ pub fn section_header(
|
|||
name: &str,
|
||||
subsection: impl Into<Option<(&'static str, &'static str)>>,
|
||||
) -> ParsedSectionHeader<'_> {
|
||||
let name = Cow::Borrowed(name);
|
||||
let name = SectionHeaderName(Cow::Borrowed(name));
|
||||
if let Some((separator, subsection_name)) = subsection.into() {
|
||||
ParsedSectionHeader {
|
||||
name,
|
||||
|
@ -30,7 +29,7 @@ pub fn section_header(
|
|||
}
|
||||
|
||||
fn name(name: &'static str) -> Event<'static> {
|
||||
Event::Key(Cow::Borrowed(name))
|
||||
Event::Key(Key(Cow::Borrowed(name)))
|
||||
}
|
||||
|
||||
fn value(value: &'static str) -> Event<'static> {
|
||||
|
|
Reference in a new issue