implement case insensitivity for names

master
Edward Shen 2021-03-06 01:23:23 -05:00
parent 6167914277
commit 9715790574
Signed by: edward
GPG Key ID: 19182661E818369F
8 changed files with 240 additions and 82 deletions

1
LICENSE-APACHE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-APACHE

1
LICENSE-MIT Symbolic link
View File

@ -0,0 +1 @@
../LICENSE-MIT

18
README.md Normal file
View 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>

View File

@ -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(&section_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(
&section_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(&section_ids.pop()?)
self.sections.remove(&section_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(&section_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())))
);
}

View File

@ -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)?;

View File

@ -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> {

View File

@ -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(())
}

View File

@ -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> {